From 9bcfc168f86999d03547e07c9b465ee6b27bb203 Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Fri, 8 May 2026 03:11:20 -0300 Subject: [PATCH 1/2] fix(core): expose master persistence degradation --- .../core/orchestration/master-orchestrator.js | 38 ++++++++++++++++++- .aiox-core/data/entity-registry.yaml | 6 +-- .aiox-core/install-manifest.yaml | 8 ++-- tests/core/master-orchestrator.test.js | 28 ++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/.aiox-core/core/orchestration/master-orchestrator.js b/.aiox-core/core/orchestration/master-orchestrator.js index dda357b532..f706097b1e 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 = { @@ -1128,17 +1130,25 @@ class MasterOrchestrator extends EventEmitter { // Errors and insights errors: this.executionState.errors, insights: this.executionState.insights, + persistence: { + available: this._persistenceAvailable, + lastError: this._persistenceError, + }, // Session insights sessionInsights: this._collectSessionInsights(), }; await fs.writeJson(this.statePath, stateToSave, { spaces: 2 }); + this._persistenceAvailable = true; + this._persistenceError = null; 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 +1444,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 +1489,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..5ddecca205 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:11:22.101Z' 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:8d86c4969eda3de3bc4ed4ac39711795f33226b447f4f8936ee39c0fea707fd0 + lastVerified: '2026-05-08T06:11:22.098Z' 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..dfd8b1e8ce 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:12:39.595Z" 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:8d86c4969eda3de3bc4ed4ac39711795f33226b447f4f8936ee39c0fea707fd0 type: core - size: 56868 + size: 57934 - 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:da7791d73e7680cc8d3bb344663d69b5a5930e848fad80d81b0bf1d8fe7777b6 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..65b7e0e829 100644 --- a/tests/core/master-orchestrator.test.js +++ b/tests/core/master-orchestrator.test.js @@ -280,6 +280,34 @@ 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', + }); + } finally { + writeJsonSpy.mockRestore(); + } }); }); From 9711e0748d4aeec8d91aa81a2f1411b0e578f1df Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Fri, 8 May 2026 03:26:23 -0300 Subject: [PATCH 2/2] fix(core): persist recovered master health --- .../core/orchestration/master-orchestrator.js | 13 +++++++------ .aiox-core/data/entity-registry.yaml | 6 +++--- .aiox-core/install-manifest.yaml | 8 ++++---- tests/core/master-orchestrator.test.js | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.aiox-core/core/orchestration/master-orchestrator.js b/.aiox-core/core/orchestration/master-orchestrator.js index f706097b1e..2d03fd9e7b 100644 --- a/.aiox-core/core/orchestration/master-orchestrator.js +++ b/.aiox-core/core/orchestration/master-orchestrator.js @@ -1086,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 = { @@ -1130,18 +1134,15 @@ class MasterOrchestrator extends EventEmitter { // Errors and insights errors: this.executionState.errors, insights: this.executionState.insights, - persistence: { - available: this._persistenceAvailable, - lastError: this._persistenceError, - }, + persistence: persistedHealth, // Session insights sessionInsights: this._collectSessionInsights(), }; await fs.writeJson(this.statePath, stateToSave, { spaces: 2 }); - this._persistenceAvailable = true; - this._persistenceError = null; + this._persistenceAvailable = persistedHealth.available; + this._persistenceError = persistedHealth.lastError; this._log('State saved successfully', { path: this.statePath }); return true; diff --git a/.aiox-core/data/entity-registry.yaml b/.aiox-core/data/entity-registry.yaml index 5ddecca205..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-08T06:11:22.101Z' + 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:8d86c4969eda3de3bc4ed4ac39711795f33226b447f4f8936ee39c0fea707fd0 - lastVerified: '2026-05-08T06:11:22.098Z' + 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 dfd8b1e8ce..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-08T06:12:39.595Z" +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:8d86c4969eda3de3bc4ed4ac39711795f33226b447f4f8936ee39c0fea707fd0 + hash: sha256:88238a9e0ef7d058efe0ca2fe7a9d0e34202bff7e409fd1a97886b0a8ccdce97 type: core - size: 57934 + 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:da7791d73e7680cc8d3bb344663d69b5a5930e848fad80d81b0bf1d8fe7777b6 + 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 65b7e0e829..8952c09f58 100644 --- a/tests/core/master-orchestrator.test.js +++ b/tests/core/master-orchestrator.test.js @@ -305,6 +305,25 @@ describe('MasterOrchestrator', () => { 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(); }