diff --git a/.aiox-core/core/orchestration/master-orchestrator.js b/.aiox-core/core/orchestration/master-orchestrator.js index dda357b532..2d03fd9e7b 100644 --- a/.aiox-core/core/orchestration/master-orchestrator.js +++ b/.aiox-core/core/orchestration/master-orchestrator.js @@ -171,6 +171,8 @@ class MasterOrchestrator extends EventEmitter { this._state = OrchestratorState.INITIALIZED; this._previousState = null; this._inFullPipeline = false; // Flag for gate evaluation during full pipeline + this._persistenceAvailable = true; + this._persistenceError = null; // Execution state this.executionState = { @@ -1084,6 +1086,10 @@ class MasterOrchestrator extends EventEmitter { async saveState() { try { await fs.ensureDir(path.dirname(this.statePath)); + const persistedHealth = { + available: true, + lastError: null, + }; // Build comprehensive state object (AC2, AC6, AC7) const stateToSave = { @@ -1128,17 +1134,22 @@ class MasterOrchestrator extends EventEmitter { // Errors and insights errors: this.executionState.errors, insights: this.executionState.insights, + persistence: persistedHealth, // Session insights sessionInsights: this._collectSessionInsights(), }; await fs.writeJson(this.statePath, stateToSave, { spaces: 2 }); + this._persistenceAvailable = persistedHealth.available; + this._persistenceError = persistedHealth.lastError; this._log('State saved successfully', { path: this.statePath }); return true; } catch (error) { - this._log(`Failed to save state: ${error.message}`, { level: 'warn' }); + this._persistenceAvailable = false; + this._persistenceError = error && error.message ? error.message : String(error); + this._log(`Failed to save state: ${this._persistenceError}`, { level: 'warn' }); return false; } } @@ -1434,6 +1445,10 @@ class MasterOrchestrator extends EventEmitter { }, errors: this.executionState.errors, insights: this.executionState.insights, + persistence: { + available: this._persistenceAvailable, + error: this._persistenceError, + }, state: this.executionState, }; } @@ -1475,9 +1490,31 @@ class MasterOrchestrator extends EventEmitter { ]), ), errors: this.executionState.errors.length, + persistence: { + available: this._persistenceAvailable, + error: this._persistenceError, + }, }; } + /** + * Whether the last state persistence operation succeeded. + * + * @returns {boolean} True when persistence is available. + */ + isPersistenceAvailable() { + return this._persistenceAvailable; + } + + /** + * Return the last state persistence error message, if any. + * + * @returns {string|null} Last persistence error message. + */ + getPersistenceError() { + return this._persistenceError; + } + // ═══════════════════════════════════════════════════════════════════════════════════ // LOGGING & CALLBACKS // ═══════════════════════════════════════════════════════════════════════════════════ diff --git a/.aiox-core/data/entity-registry.yaml b/.aiox-core/data/entity-registry.yaml index 0a1da2d7d5..8a1a49b130 100644 --- a/.aiox-core/data/entity-registry.yaml +++ b/.aiox-core/data/entity-registry.yaml @@ -1,6 +1,6 @@ metadata: version: 1.0.0 - lastUpdated: '2026-05-07T21:02:02.654Z' + lastUpdated: '2026-05-08T06:26:24.449Z' entityCount: 746 checksumAlgorithm: sha256 resolutionRate: 100 @@ -9745,8 +9745,8 @@ entities: score: 0.4 constraints: [] extensionPoints: [] - checksum: sha256:61c988509c2edc03c1cd4dec333b284f7d15273f3e799c56950706a2e88f341b - lastVerified: '2026-05-07T10:44:44.074Z' + checksum: sha256:88238a9e0ef7d058efe0ca2fe7a9d0e34202bff7e409fd1a97886b0a8ccdce97 + lastVerified: '2026-05-08T06:26:24.445Z' message-formatter: path: .aiox-core/core/orchestration/message-formatter.js layer: L1 diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index a1220558e7..9a5aada78a 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -8,7 +8,7 @@ # - File types for categorization # version: 5.1.15 -generated_at: "2026-05-08T05:30:43.651Z" +generated_at: "2026-05-08T06:26:53.273Z" generator: scripts/generate-install-manifest.js file_count: 1111 files: @@ -917,9 +917,9 @@ files: type: core size: 9382 - path: core/orchestration/master-orchestrator.js - hash: sha256:29b69047fe780751ae89f6c593cb835254e2e8a536f5581473f474115deec496 + hash: sha256:88238a9e0ef7d058efe0ca2fe7a9d0e34202bff7e409fd1a97886b0a8ccdce97 type: core - size: 56868 + size: 57977 - path: core/orchestration/message-formatter.js hash: sha256:b7413c04fa22db1c5fc2f5c2aa47bb8ca0374e079894a44df21b733da6c258ae type: core @@ -1241,7 +1241,7 @@ files: type: data size: 9590 - path: data/entity-registry.yaml - hash: sha256:416ffc477206f2b75700a800a35b6af51269f73b3bbc9952a155b1f350089030 + hash: sha256:1c8e010516647898377715d160234094bc262b0812cf17c96d7d1d4d36c7b4e8 type: data size: 522368 - path: data/learned-patterns.yaml diff --git a/tests/core/master-orchestrator.test.js b/tests/core/master-orchestrator.test.js index fcc827ce68..8952c09f58 100644 --- a/tests/core/master-orchestrator.test.js +++ b/tests/core/master-orchestrator.test.js @@ -280,6 +280,53 @@ describe('MasterOrchestrator', () => { await orchestrator.initialize(); const result = await orchestrator.saveState(); expect(result).toBe(true); + expect(orchestrator.isPersistenceAvailable()).toBe(true); + expect(orchestrator.getPersistenceError()).toBeNull(); + expect(orchestrator.getStatus().persistence).toEqual({ + available: true, + error: null, + }); + }); + + it('should expose persistence degradation when state save fails', async () => { + const writeJsonSpy = jest.spyOn(fs, 'writeJson').mockRejectedValueOnce(new Error('disk full')); + + try { + const result = await orchestrator.saveState(); + + expect(result).toBe(false); + expect(orchestrator.isPersistenceAvailable()).toBe(false); + expect(orchestrator.getPersistenceError()).toBe('disk full'); + expect(orchestrator.getStatus().persistence).toEqual({ + available: false, + error: 'disk full', + }); + expect(orchestrator.finalize().persistence).toEqual({ + available: false, + error: 'disk full', + }); + + writeJsonSpy.mockResolvedValueOnce(); + const recoveryResult = await orchestrator.saveState(); + + expect(recoveryResult).toBe(true); + expect(orchestrator.isPersistenceAvailable()).toBe(true); + expect(orchestrator.getPersistenceError()).toBeNull(); + expect(orchestrator.getStatus().persistence).toEqual({ + available: true, + error: null, + }); + expect(orchestrator.finalize().persistence).toEqual({ + available: true, + error: null, + }); + expect(writeJsonSpy.mock.calls[1][1].persistence).toEqual({ + available: true, + lastError: null, + }); + } finally { + writeJsonSpy.mockRestore(); + } }); });