Skip to content

Commit 9496767

Browse files
authored
fix(core): expose master persistence degradation (#698)
* fix(core): expose master persistence degradation * fix(core): persist recovered master health
1 parent a88405c commit 9496767

4 files changed

Lines changed: 92 additions & 8 deletions

File tree

.aiox-core/core/orchestration/master-orchestrator.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ class MasterOrchestrator extends EventEmitter {
171171
this._state = OrchestratorState.INITIALIZED;
172172
this._previousState = null;
173173
this._inFullPipeline = false; // Flag for gate evaluation during full pipeline
174+
this._persistenceAvailable = true;
175+
this._persistenceError = null;
174176

175177
// Execution state
176178
this.executionState = {
@@ -1084,6 +1086,10 @@ class MasterOrchestrator extends EventEmitter {
10841086
async saveState() {
10851087
try {
10861088
await fs.ensureDir(path.dirname(this.statePath));
1089+
const persistedHealth = {
1090+
available: true,
1091+
lastError: null,
1092+
};
10871093

10881094
// Build comprehensive state object (AC2, AC6, AC7)
10891095
const stateToSave = {
@@ -1128,17 +1134,22 @@ class MasterOrchestrator extends EventEmitter {
11281134
// Errors and insights
11291135
errors: this.executionState.errors,
11301136
insights: this.executionState.insights,
1137+
persistence: persistedHealth,
11311138

11321139
// Session insights
11331140
sessionInsights: this._collectSessionInsights(),
11341141
};
11351142

11361143
await fs.writeJson(this.statePath, stateToSave, { spaces: 2 });
1144+
this._persistenceAvailable = persistedHealth.available;
1145+
this._persistenceError = persistedHealth.lastError;
11371146
this._log('State saved successfully', { path: this.statePath });
11381147

11391148
return true;
11401149
} catch (error) {
1141-
this._log(`Failed to save state: ${error.message}`, { level: 'warn' });
1150+
this._persistenceAvailable = false;
1151+
this._persistenceError = error && error.message ? error.message : String(error);
1152+
this._log(`Failed to save state: ${this._persistenceError}`, { level: 'warn' });
11421153
return false;
11431154
}
11441155
}
@@ -1434,6 +1445,10 @@ class MasterOrchestrator extends EventEmitter {
14341445
},
14351446
errors: this.executionState.errors,
14361447
insights: this.executionState.insights,
1448+
persistence: {
1449+
available: this._persistenceAvailable,
1450+
error: this._persistenceError,
1451+
},
14371452
state: this.executionState,
14381453
};
14391454
}
@@ -1475,9 +1490,31 @@ class MasterOrchestrator extends EventEmitter {
14751490
]),
14761491
),
14771492
errors: this.executionState.errors.length,
1493+
persistence: {
1494+
available: this._persistenceAvailable,
1495+
error: this._persistenceError,
1496+
},
14781497
};
14791498
}
14801499

1500+
/**
1501+
* Whether the last state persistence operation succeeded.
1502+
*
1503+
* @returns {boolean} True when persistence is available.
1504+
*/
1505+
isPersistenceAvailable() {
1506+
return this._persistenceAvailable;
1507+
}
1508+
1509+
/**
1510+
* Return the last state persistence error message, if any.
1511+
*
1512+
* @returns {string|null} Last persistence error message.
1513+
*/
1514+
getPersistenceError() {
1515+
return this._persistenceError;
1516+
}
1517+
14811518
// ═══════════════════════════════════════════════════════════════════════════════════
14821519
// LOGGING & CALLBACKS
14831520
// ═══════════════════════════════════════════════════════════════════════════════════

.aiox-core/data/entity-registry.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
metadata:
22
version: 1.0.0
3-
lastUpdated: '2026-05-07T21:02:02.654Z'
3+
lastUpdated: '2026-05-08T06:26:24.449Z'
44
entityCount: 746
55
checksumAlgorithm: sha256
66
resolutionRate: 100
@@ -9745,8 +9745,8 @@ entities:
97459745
score: 0.4
97469746
constraints: []
97479747
extensionPoints: []
9748-
checksum: sha256:61c988509c2edc03c1cd4dec333b284f7d15273f3e799c56950706a2e88f341b
9749-
lastVerified: '2026-05-07T10:44:44.074Z'
9748+
checksum: sha256:88238a9e0ef7d058efe0ca2fe7a9d0e34202bff7e409fd1a97886b0a8ccdce97
9749+
lastVerified: '2026-05-08T06:26:24.445Z'
97509750
message-formatter:
97519751
path: .aiox-core/core/orchestration/message-formatter.js
97529752
layer: L1

.aiox-core/install-manifest.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# - File types for categorization
99
#
1010
version: 5.1.15
11-
generated_at: "2026-05-08T05:30:43.651Z"
11+
generated_at: "2026-05-08T06:26:53.273Z"
1212
generator: scripts/generate-install-manifest.js
1313
file_count: 1111
1414
files:
@@ -917,9 +917,9 @@ files:
917917
type: core
918918
size: 9382
919919
- path: core/orchestration/master-orchestrator.js
920-
hash: sha256:29b69047fe780751ae89f6c593cb835254e2e8a536f5581473f474115deec496
920+
hash: sha256:88238a9e0ef7d058efe0ca2fe7a9d0e34202bff7e409fd1a97886b0a8ccdce97
921921
type: core
922-
size: 56868
922+
size: 57977
923923
- path: core/orchestration/message-formatter.js
924924
hash: sha256:b7413c04fa22db1c5fc2f5c2aa47bb8ca0374e079894a44df21b733da6c258ae
925925
type: core
@@ -1241,7 +1241,7 @@ files:
12411241
type: data
12421242
size: 9590
12431243
- path: data/entity-registry.yaml
1244-
hash: sha256:416ffc477206f2b75700a800a35b6af51269f73b3bbc9952a155b1f350089030
1244+
hash: sha256:1c8e010516647898377715d160234094bc262b0812cf17c96d7d1d4d36c7b4e8
12451245
type: data
12461246
size: 522368
12471247
- path: data/learned-patterns.yaml

tests/core/master-orchestrator.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,53 @@ describe('MasterOrchestrator', () => {
280280
await orchestrator.initialize();
281281
const result = await orchestrator.saveState();
282282
expect(result).toBe(true);
283+
expect(orchestrator.isPersistenceAvailable()).toBe(true);
284+
expect(orchestrator.getPersistenceError()).toBeNull();
285+
expect(orchestrator.getStatus().persistence).toEqual({
286+
available: true,
287+
error: null,
288+
});
289+
});
290+
291+
it('should expose persistence degradation when state save fails', async () => {
292+
const writeJsonSpy = jest.spyOn(fs, 'writeJson').mockRejectedValueOnce(new Error('disk full'));
293+
294+
try {
295+
const result = await orchestrator.saveState();
296+
297+
expect(result).toBe(false);
298+
expect(orchestrator.isPersistenceAvailable()).toBe(false);
299+
expect(orchestrator.getPersistenceError()).toBe('disk full');
300+
expect(orchestrator.getStatus().persistence).toEqual({
301+
available: false,
302+
error: 'disk full',
303+
});
304+
expect(orchestrator.finalize().persistence).toEqual({
305+
available: false,
306+
error: 'disk full',
307+
});
308+
309+
writeJsonSpy.mockResolvedValueOnce();
310+
const recoveryResult = await orchestrator.saveState();
311+
312+
expect(recoveryResult).toBe(true);
313+
expect(orchestrator.isPersistenceAvailable()).toBe(true);
314+
expect(orchestrator.getPersistenceError()).toBeNull();
315+
expect(orchestrator.getStatus().persistence).toEqual({
316+
available: true,
317+
error: null,
318+
});
319+
expect(orchestrator.finalize().persistence).toEqual({
320+
available: true,
321+
error: null,
322+
});
323+
expect(writeJsonSpy.mock.calls[1][1].persistence).toEqual({
324+
available: true,
325+
lastError: null,
326+
});
327+
} finally {
328+
writeJsonSpy.mockRestore();
329+
}
283330
});
284331
});
285332

0 commit comments

Comments
 (0)