@xioflow/kernel 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/domain.d.ts +40 -6
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +388 -24
- package/dist/domain.js.map +1 -1
- package/dist/driver/node-driver.d.ts +10 -24
- package/dist/driver/node-driver.d.ts.map +1 -1
- package/dist/driver/node-driver.js +201 -280
- package/dist/driver/node-driver.js.map +1 -1
- package/dist/driver/process-facts.d.ts +25 -0
- package/dist/driver/process-facts.d.ts.map +1 -0
- package/dist/driver/process-facts.js +164 -0
- package/dist/driver/process-facts.js.map +1 -0
- package/dist/driver/sampler.d.ts +51 -0
- package/dist/driver/sampler.d.ts.map +1 -0
- package/dist/driver/sampler.js +224 -0
- package/dist/driver/sampler.js.map +1 -0
- package/dist/driver/types.d.ts +21 -1
- package/dist/driver/types.d.ts.map +1 -1
- package/dist/recovery/engine.d.ts.map +1 -1
- package/dist/recovery/engine.js +103 -19
- package/dist/recovery/engine.js.map +1 -1
- package/dist/store/sqlite.d.ts +12 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +162 -12
- package/dist/store/sqlite.js.map +1 -1
- package/dist/supervisor/supervisor.d.ts +15 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -1
- package/dist/supervisor/supervisor.js +619 -214
- package/dist/supervisor/supervisor.js.map +1 -1
- package/dist/testing/contract-suite.d.ts.map +1 -1
- package/dist/testing/contract-suite.js +301 -11
- package/dist/testing/contract-suite.js.map +1 -1
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/dist/recovery/engine.js
CHANGED
|
@@ -4,6 +4,7 @@ export class RecoveryEngine {
|
|
|
4
4
|
constructor(domain, driver) {
|
|
5
5
|
this.domain = domain;
|
|
6
6
|
this.driver = driver;
|
|
7
|
+
this.domain.setDriver?.(driver);
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* 恢复协议:对未终结操作现场核查并安全推进至终态或保留隔离
|
|
@@ -29,7 +30,7 @@ export class RecoveryEngine {
|
|
|
29
30
|
completedAt: new Date().toISOString(),
|
|
30
31
|
};
|
|
31
32
|
store.recordOperationResult(op.id, failResult, true);
|
|
32
|
-
this.domain.
|
|
33
|
+
this.domain.internalReleaseResources(op.id, op.requiredResources);
|
|
33
34
|
report.recoveredOperations.push({
|
|
34
35
|
opId: op.id,
|
|
35
36
|
action: 'cleaned_unspawned',
|
|
@@ -38,26 +39,31 @@ export class RecoveryEngine {
|
|
|
38
39
|
continue;
|
|
39
40
|
}
|
|
40
41
|
if (op.processIdentity) {
|
|
42
|
+
const spawnTimeMs = op.processIdentity.spawnTime
|
|
43
|
+
? new Date(op.processIdentity.spawnTime).getTime()
|
|
44
|
+
: NaN;
|
|
45
|
+
const calcDurationMs = () => (!isNaN(spawnTimeMs) ? Math.max(0, Date.now() - spawnTimeMs) : 0);
|
|
41
46
|
// 场景 2-5:存在进程身份,现场向驱动核实身份
|
|
42
47
|
const verification = await this.driver.verifyIdentity(op.processIdentity);
|
|
43
48
|
if (verification === 'is_original_process') {
|
|
44
49
|
// 进程依然存活,发送停止确认流水线
|
|
45
50
|
const stopResult = await this.driver.terminate(op.processIdentity, 2000);
|
|
46
|
-
if (stopResult.stopped) {
|
|
51
|
+
if (stopResult.stopped === 'confirmed_stopped') {
|
|
47
52
|
const cancelResult = {
|
|
48
53
|
kind: 'process',
|
|
49
54
|
status: 'cancelled',
|
|
50
55
|
exitCode: null,
|
|
51
|
-
signal:
|
|
56
|
+
signal: null,
|
|
52
57
|
stdout: '',
|
|
53
|
-
stderr: '
|
|
58
|
+
stderr: '',
|
|
59
|
+
evidence: 'unobserved',
|
|
54
60
|
isTruncated: false,
|
|
55
61
|
identityVerification: 'is_original_process',
|
|
56
|
-
durationMs:
|
|
62
|
+
durationMs: calcDurationMs(),
|
|
57
63
|
completedAt: new Date().toISOString(),
|
|
58
64
|
};
|
|
59
65
|
store.recordOperationResult(op.id, cancelResult, true);
|
|
60
|
-
this.domain.
|
|
66
|
+
this.domain.internalReleaseResources(op.id, op.requiredResources);
|
|
61
67
|
report.recoveredOperations.push({
|
|
62
68
|
opId: op.id,
|
|
63
69
|
action: 'stopped_alive_process',
|
|
@@ -71,7 +77,7 @@ export class RecoveryEngine {
|
|
|
71
77
|
status: 'indeterminate',
|
|
72
78
|
reason: 'Alive process failed to stop during recovery',
|
|
73
79
|
recoveryGuidance: 'Residual PID detected. Check system processes manually.',
|
|
74
|
-
durationMs:
|
|
80
|
+
durationMs: calcDurationMs(),
|
|
75
81
|
completedAt: new Date().toISOString(),
|
|
76
82
|
};
|
|
77
83
|
store.recordOperationResult(op.id, indetResult, false);
|
|
@@ -85,20 +91,77 @@ export class RecoveryEngine {
|
|
|
85
91
|
else if (verification === 'not_original_process') {
|
|
86
92
|
// 进程已确定死亡。但"leader 死了"不等于"组空了":被 SIGKILL 的 leader 会
|
|
87
93
|
// 变成僵尸,它 fork 出来的后代可能还在运行,而这些进程已经没有 owner。
|
|
88
|
-
//
|
|
94
|
+
// 先做 N2 证据核验(bootId + 成员启动时间),证据不足或异常时绝不盲目发信号,判 indeterminate 保留租约;
|
|
95
|
+
// 证据齐全时才按 pgid 定向清场;清不掉如实隔离。
|
|
89
96
|
const pgid = op.processIdentity.pgid;
|
|
90
97
|
let reapedOrphans = false;
|
|
91
98
|
if (pgid !== undefined && this.isGroupAlive(pgid)) {
|
|
99
|
+
// N2 检查项 1:bootId 跨宿主重启核验
|
|
100
|
+
if (op.processIdentity.bootId && this.driver.readBootId) {
|
|
101
|
+
const currentBootId = await this.driver.readBootId();
|
|
102
|
+
if (currentBootId && op.processIdentity.bootId !== currentBootId) {
|
|
103
|
+
// 宿主在崩溃后已发生重启,当前同号 pgid 属于重启后的全新无关进程组,严禁发送信号
|
|
104
|
+
const indetResult = {
|
|
105
|
+
kind: 'indeterminate',
|
|
106
|
+
status: 'indeterminate',
|
|
107
|
+
reason: `Host bootId changed (recorded: '${op.processIdentity.bootId}', current: '${currentBootId}'); process group ${pgid} belongs to a different boot session`,
|
|
108
|
+
recoveryGuidance: 'Host was rebooted after the crash. Do not terminate external process groups; manual inspection required.',
|
|
109
|
+
durationMs: calcDurationMs(),
|
|
110
|
+
completedAt: new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
store.recordOperationResult(op.id, indetResult, false);
|
|
113
|
+
report.recoveredOperations.push({
|
|
114
|
+
opId: op.id,
|
|
115
|
+
action: 'isolated_indeterminate',
|
|
116
|
+
resourcesReleased: false,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// N2 检查项 2:成员启动时间核验(防同一次 boot 下的 PGID 复用)
|
|
122
|
+
if (this.driver.getGroupEvidence) {
|
|
123
|
+
const members = await this.driver.getGroupEvidence(pgid);
|
|
124
|
+
if (members.length > 0) {
|
|
125
|
+
const opSpawnMs = Date.parse(op.processIdentity.spawnTime);
|
|
126
|
+
if (!isNaN(opSpawnMs)) {
|
|
127
|
+
let hasEarlierProcess = false;
|
|
128
|
+
for (const m of members) {
|
|
129
|
+
if (m.startTimeMs !== null && m.startTimeMs < opSpawnMs - 1000) {
|
|
130
|
+
hasEarlierProcess = true;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (hasEarlierProcess) {
|
|
135
|
+
// 组成员启动时间早于该 op 启动时间,说明当前 pgid 属于外部更早创建的无关进程组
|
|
136
|
+
const indetResult = {
|
|
137
|
+
kind: 'indeterminate',
|
|
138
|
+
status: 'indeterminate',
|
|
139
|
+
reason: `Process group ${pgid} membership start time is earlier than operation spawn time; suspected PGID reuse`,
|
|
140
|
+
recoveryGuidance: 'External process group occupies the recorded PGID. Do not terminate; verify process ownership manually.',
|
|
141
|
+
durationMs: calcDurationMs(),
|
|
142
|
+
completedAt: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
store.recordOperationResult(op.id, indetResult, false);
|
|
145
|
+
report.recoveredOperations.push({
|
|
146
|
+
opId: op.id,
|
|
147
|
+
action: 'isolated_indeterminate',
|
|
148
|
+
resourcesReleased: false,
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
92
155
|
const reaped = this.driver.terminateGroup
|
|
93
156
|
? await this.driver.terminateGroup(pgid, 2000)
|
|
94
|
-
: { stopped:
|
|
95
|
-
if (
|
|
157
|
+
: { stopped: 'not_stopped', scope: 'unknown' };
|
|
158
|
+
if (reaped.stopped !== 'confirmed_stopped') {
|
|
96
159
|
const indetResult = {
|
|
97
160
|
kind: 'indeterminate',
|
|
98
161
|
status: 'indeterminate',
|
|
99
162
|
reason: 'Owner is dead but its process group could not be confirmed stopped',
|
|
100
163
|
recoveryGuidance: 'Residual processes from the crashed owner are still alive; inspect them before retrying.',
|
|
101
|
-
durationMs:
|
|
164
|
+
durationMs: calcDurationMs(),
|
|
102
165
|
completedAt: new Date().toISOString(),
|
|
103
166
|
};
|
|
104
167
|
store.recordOperationResult(op.id, indetResult, false);
|
|
@@ -114,19 +177,19 @@ export class RecoveryEngine {
|
|
|
114
177
|
const deadResult = {
|
|
115
178
|
kind: 'process',
|
|
116
179
|
status: 'failed',
|
|
117
|
-
exitCode:
|
|
118
|
-
signal:
|
|
180
|
+
exitCode: null,
|
|
181
|
+
signal: null,
|
|
119
182
|
stdout: '',
|
|
120
|
-
stderr: reapedOrphans
|
|
121
|
-
|
|
122
|
-
|
|
183
|
+
stderr: reapedOrphans ? 'orphaned descendants were reaped during recovery' : '',
|
|
184
|
+
evidence: 'unobserved',
|
|
185
|
+
residualProcessesReaped: reapedOrphans,
|
|
123
186
|
isTruncated: false,
|
|
124
187
|
identityVerification: 'not_original_process',
|
|
125
|
-
durationMs:
|
|
188
|
+
durationMs: calcDurationMs(),
|
|
126
189
|
completedAt: new Date().toISOString(),
|
|
127
190
|
};
|
|
128
191
|
store.recordOperationResult(op.id, deadResult, true);
|
|
129
|
-
this.domain.
|
|
192
|
+
this.domain.internalReleaseResources(op.id, op.requiredResources);
|
|
130
193
|
report.recoveredOperations.push({
|
|
131
194
|
opId: op.id,
|
|
132
195
|
action: 'marked_dead',
|
|
@@ -140,7 +203,7 @@ export class RecoveryEngine {
|
|
|
140
203
|
status: 'indeterminate',
|
|
141
204
|
reason: 'Cannot determine process identity after restart',
|
|
142
205
|
recoveryGuidance: 'Manual inspection required. Resource isolation remains active.',
|
|
143
|
-
durationMs:
|
|
206
|
+
durationMs: calcDurationMs(),
|
|
144
207
|
completedAt: new Date().toISOString(),
|
|
145
208
|
};
|
|
146
209
|
store.recordOperationResult(op.id, indetResult, false);
|
|
@@ -152,10 +215,31 @@ export class RecoveryEngine {
|
|
|
152
215
|
}
|
|
153
216
|
}
|
|
154
217
|
}
|
|
218
|
+
// 5. [Run 状态收敛] (ARCHITECTURE §3.2 第 5 步)
|
|
219
|
+
// 本轮裁决后,所属 Run 若已无未终结操作:
|
|
220
|
+
// 存在 indeterminate ⇒ Run 置 indeterminate;
|
|
221
|
+
// 否则置 failed(terminationReason = crash_detected)。禁止让 Run 永久停留在 running
|
|
222
|
+
const activeRuns = store.getActiveRuns(this.domain.domainId);
|
|
223
|
+
for (const run of activeRuns) {
|
|
224
|
+
const runOps = store.getOperationsByRun(run.id);
|
|
225
|
+
const hasUnfinished = runOps.some((o) => o.status !== 'done');
|
|
226
|
+
if (!hasUnfinished) {
|
|
227
|
+
const hasIndeterminate = runOps.some((o) => o.result?.status === 'indeterminate');
|
|
228
|
+
if (hasIndeterminate) {
|
|
229
|
+
store.updateRunStatus(run.id, 'indeterminate', undefined, new Date().toISOString());
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
store.updateRunStatus(run.id, 'failed', 'crash_detected', new Date().toISOString());
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
155
236
|
return report;
|
|
156
237
|
}
|
|
157
238
|
/** 进程组是否仍存在(僵尸也算存在;具体是否"能工作"由驱动的 terminateGroup 判定)。 */
|
|
158
239
|
isGroupAlive(pgid) {
|
|
240
|
+
if (typeof this.driver.isGroupAlive === 'function') {
|
|
241
|
+
return this.driver.isGroupAlive(pgid);
|
|
242
|
+
}
|
|
159
243
|
try {
|
|
160
244
|
process.kill(-pgid, 0);
|
|
161
245
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/recovery/engine.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,cAAc;IAEN,MAAM;IACN,MAAM;IAFzB,YACmB,MAAuB,EACvB,MAAsB;sBADtB,MAAM;sBACN,MAAM;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/recovery/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export declare class SqliteStore {
|
|
|
7
7
|
private initSchema;
|
|
8
8
|
private inTransaction;
|
|
9
9
|
transaction<T>(fn: () => T): T;
|
|
10
|
+
/**
|
|
11
|
+
* 触发代际栅栏,永久阻断后续所有写操作 (P0-11)
|
|
12
|
+
*/
|
|
13
|
+
fence(): void;
|
|
10
14
|
/**
|
|
11
15
|
* @internal 仅供测试套件模拟跨代/代际穿透场景,严禁在生产运行时直接调用
|
|
12
16
|
*/
|
|
@@ -30,8 +34,12 @@ export declare class SqliteStore {
|
|
|
30
34
|
*/
|
|
31
35
|
reportRunSucceeded(runId: string): void;
|
|
32
36
|
reportRunFailed(runId: string, reason?: TerminationReason): void;
|
|
37
|
+
reportRunCancelled(runId: string, reason?: TerminationReason): void;
|
|
38
|
+
getActiveRuns(domainId: string): Run[];
|
|
33
39
|
registerOperationIntent(op: Operation, domainId: string): void;
|
|
40
|
+
recordPreIntentCancelledOperation(op: Operation, domainId: string, result: OperationResult): void;
|
|
34
41
|
updateOperationStatus(opId: string, status: OperationStatus, processIdentity?: Operation['processIdentity'], outputRef?: string): void;
|
|
42
|
+
updateOperationResult(opId: string, result: OperationResult): void;
|
|
35
43
|
recordOperationResult(opId: string, result: OperationResult, releaseResources?: boolean): void;
|
|
36
44
|
getOperation(id: string): (Operation & {
|
|
37
45
|
domainId: string;
|
|
@@ -42,12 +50,16 @@ export declare class SqliteStore {
|
|
|
42
50
|
getUnfinishedOperations(domainId: string): (Operation & {
|
|
43
51
|
domainId: string;
|
|
44
52
|
})[];
|
|
53
|
+
getAllOperations(domainId: string): (Operation & {
|
|
54
|
+
domainId: string;
|
|
55
|
+
})[];
|
|
45
56
|
getPersistedResourceLeases(domainId: string): (ResourceLease & {
|
|
46
57
|
budget?: ResourceBudget;
|
|
47
58
|
})[];
|
|
48
59
|
releaseResourceLease(operationId: string, resourceId?: string): void;
|
|
49
60
|
recordEventAndTransitionState(event: Omit<JournalEvent, 'seq'>): number;
|
|
50
61
|
getJournalEvents(domainId: string, fromSeq?: number): JournalEvent[];
|
|
62
|
+
getEventsByRun(runId: string): JournalEvent[];
|
|
51
63
|
close(): void;
|
|
52
64
|
}
|
|
53
65
|
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,IAAI,EACJ,GAAG,EACH,SAAS,EACT,eAAe,EACf,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,cAAc,EAIf,MAAM,aAAa,CAAC;AAErB,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,YAAY,CAAuB;IAE3C,YAAY,MAAM,EAAE,MAAM,EAIzB;IAED,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa,CAAS;IAEvB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAgBpC;IAED;;OAEG;IACI,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjE;IAED;;OAEG;IACI,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEjD;IAEM,eAAe,IAAI,MAAM,GAAG,IAAI,CAEtC;IAEM,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAYpD;IAEM,iBAAiB,CACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAc,EACrB,KAAK,GAAE,OAAe,GACrB,WAAW,CA8Db;IAEM,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAOhE;IAEM,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOhD;IAEM,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAgBhC;IAEM,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAWtC;IAEM,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"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"}
|
package/dist/store/sqlite.js
CHANGED
|
@@ -38,6 +38,12 @@ export class SqliteStore {
|
|
|
38
38
|
this.inTransaction = false;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* 触发代际栅栏,永久阻断后续所有写操作 (P0-11)
|
|
43
|
+
*/
|
|
44
|
+
fence() {
|
|
45
|
+
this.currentEpoch = -1;
|
|
46
|
+
}
|
|
41
47
|
/**
|
|
42
48
|
* @internal 仅供测试套件模拟跨代/代际穿透场景,严禁在生产运行时直接调用
|
|
43
49
|
*/
|
|
@@ -174,6 +180,12 @@ export class SqliteStore {
|
|
|
174
180
|
}
|
|
175
181
|
saveRun(run) {
|
|
176
182
|
this.verifyEpochFencing(run.domainId);
|
|
183
|
+
const existing = this.getRun(run.id);
|
|
184
|
+
if (existing && (existing.status === 'succeeded' || existing.status === 'failed' || existing.status === 'cancelled' || existing.status === 'indeterminate')) {
|
|
185
|
+
if (existing.status !== run.status) {
|
|
186
|
+
throw new Error(`Cannot transition Run "${run.id}" from terminal status "${existing.status}" to "${run.status}".`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
177
189
|
const sanitizedConfig = sanitizeConfigSnapshot(run.configSnapshotWhiteList);
|
|
178
190
|
const stmt = this.db.prepare(`
|
|
179
191
|
INSERT INTO runs (id, task_id, domain_id, owner, status, termination_reason, started_at, ended_at, config_snapshot)
|
|
@@ -208,6 +220,12 @@ export class SqliteStore {
|
|
|
208
220
|
const run = this.getRun(runId);
|
|
209
221
|
if (run) {
|
|
210
222
|
this.verifyEpochFencing(run.domainId);
|
|
223
|
+
if (run.status === 'succeeded' || run.status === 'failed' || run.status === 'cancelled' || run.status === 'indeterminate') {
|
|
224
|
+
if (run.status === status) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
throw new Error(`Cannot transition Run "${runId}" from terminal status "${run.status}" to "${status}".`);
|
|
228
|
+
}
|
|
211
229
|
}
|
|
212
230
|
const stmt = this.db.prepare(`
|
|
213
231
|
UPDATE runs
|
|
@@ -235,6 +253,11 @@ export class SqliteStore {
|
|
|
235
253
|
if (!run)
|
|
236
254
|
throw new Error(`Run '${runId}' not found`);
|
|
237
255
|
this.verifyEpochFencing(run.domainId);
|
|
256
|
+
if (run.status === 'succeeded')
|
|
257
|
+
return;
|
|
258
|
+
if (run.status === 'failed' || run.status === 'cancelled' || run.status === 'indeterminate') {
|
|
259
|
+
throw new Error(`Cannot report run '${runId}' succeeded: already finalized with status '${run.status}'`);
|
|
260
|
+
}
|
|
238
261
|
const ops = this.getOperationsByRun(runId);
|
|
239
262
|
const unfinished = ops.filter((op) => op.status !== 'done');
|
|
240
263
|
if (unfinished.length > 0) {
|
|
@@ -242,7 +265,8 @@ export class SqliteStore {
|
|
|
242
265
|
}
|
|
243
266
|
const indeterminate = ops.filter((op) => op.result?.status === 'indeterminate');
|
|
244
267
|
if (indeterminate.length > 0) {
|
|
245
|
-
|
|
268
|
+
const indetReason = indeterminate[0].result?.terminationReason || undefined;
|
|
269
|
+
this.updateRunStatus(runId, 'indeterminate', indetReason, new Date().toISOString());
|
|
246
270
|
return;
|
|
247
271
|
}
|
|
248
272
|
this.updateRunStatus(runId, 'succeeded', 'completed', new Date().toISOString());
|
|
@@ -254,32 +278,68 @@ export class SqliteStore {
|
|
|
254
278
|
if (!run)
|
|
255
279
|
throw new Error(`Run '${runId}' not found`);
|
|
256
280
|
this.verifyEpochFencing(run.domainId);
|
|
281
|
+
if (run.status === 'failed')
|
|
282
|
+
return;
|
|
283
|
+
if (run.status === 'succeeded' || run.status === 'cancelled' || run.status === 'indeterminate') {
|
|
284
|
+
throw new Error(`Cannot report run '${runId}' failed: already finalized with status '${run.status}'`);
|
|
285
|
+
}
|
|
257
286
|
this.updateRunStatus(runId, 'failed', reason, new Date().toISOString());
|
|
258
287
|
});
|
|
259
288
|
}
|
|
289
|
+
reportRunCancelled(runId, reason = 'user_cancelled') {
|
|
290
|
+
this.transaction(() => {
|
|
291
|
+
const run = this.getRun(runId);
|
|
292
|
+
if (!run)
|
|
293
|
+
throw new Error(`Run '${runId}' not found`);
|
|
294
|
+
this.verifyEpochFencing(run.domainId);
|
|
295
|
+
if (run.status === 'succeeded' || run.status === 'failed' || run.status === 'cancelled' || run.status === 'indeterminate') {
|
|
296
|
+
throw new Error(`Cannot report run '${runId}' cancelled: already finalized with status '${run.status}'`);
|
|
297
|
+
}
|
|
298
|
+
this.updateRunStatus(runId, 'cancelled', reason, new Date().toISOString());
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
getActiveRuns(domainId) {
|
|
302
|
+
const stmt = this.db.prepare('SELECT * FROM runs WHERE domain_id = ? AND status = ?');
|
|
303
|
+
const rows = stmt.all(domainId, 'running');
|
|
304
|
+
return rows.map((row) => ({
|
|
305
|
+
id: row.id,
|
|
306
|
+
taskId: row.task_id,
|
|
307
|
+
domainId: row.domain_id,
|
|
308
|
+
owner: row.owner,
|
|
309
|
+
status: row.status,
|
|
310
|
+
terminationReason: row.termination_reason,
|
|
311
|
+
startedAt: row.started_at,
|
|
312
|
+
endedAt: row.ended_at || undefined,
|
|
313
|
+
configSnapshotWhiteList: row.config_snapshot ? JSON.parse(row.config_snapshot) : undefined,
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
260
316
|
registerOperationIntent(op, domainId) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (!this.getRun(op.runId)) {
|
|
317
|
+
const run = this.getRun(op.runId);
|
|
318
|
+
if (!run) {
|
|
264
319
|
throw new Error(`Run "${op.runId}" is not registered in domain "${domainId}". ` +
|
|
265
320
|
'Register the task and run first (store.saveTask() + store.saveRun()), then execute operations for that run.');
|
|
266
321
|
}
|
|
322
|
+
if (run.status === 'succeeded' || run.status === 'failed' || run.status === 'cancelled' || run.status === 'indeterminate') {
|
|
323
|
+
throw new Error(`Cannot register operation "${op.id}" for Run "${op.runId}" because the Run is already finalized with status "${run.status}".`);
|
|
324
|
+
}
|
|
267
325
|
this.transaction(() => {
|
|
268
326
|
this.verifyEpochFencing(domainId);
|
|
327
|
+
const now = new Date().toISOString();
|
|
269
328
|
// 1. 写入 operation 记录,初始状态 intent_registered
|
|
270
329
|
const opStmt = this.db.prepare(`
|
|
271
330
|
INSERT INTO operations (id, run_id, domain_id, kind, name, input_fingerprint, status, required_resources, timeout_ms, resource_budget, output_ref, process_identity, result)
|
|
272
331
|
VALUES (?, ?, ?, ?, ?, ?, 'intent_registered', ?, ?, ?, ?, ?, ?)
|
|
273
332
|
`);
|
|
274
|
-
opStmt.run(op.id, op.runId, domainId, op.kind, op.name, op.inputFingerprint, JSON.stringify(op.requiredResources), op.timeoutMs || null, op.resourceBudget ? JSON.stringify(op.resourceBudget) : null, op.outputRef || null, op.processIdentity ? JSON.stringify(op.processIdentity) : null, op.result ? JSON.stringify(op.result) : null);
|
|
333
|
+
opStmt.run(op.id, op.runId, domainId, op.kind, op.name, op.inputFingerprint, JSON.stringify(op.requiredResources || []), op.timeoutMs || null, op.resourceBudget ? JSON.stringify(op.resourceBudget) : null, op.outputRef || null, op.processIdentity ? JSON.stringify(op.processIdentity) : null, op.result ? JSON.stringify(op.result) : null);
|
|
275
334
|
// 2. 写入 resource_leases 表记录排他资源占用
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
335
|
+
if (op.requiredResources && op.requiredResources.length > 0) {
|
|
336
|
+
const leaseStmt = this.db.prepare(`
|
|
337
|
+
INSERT INTO resource_leases (resource_id, operation_id, domain_id, acquired_at, budget)
|
|
338
|
+
VALUES (?, ?, ?, ?, ?)
|
|
339
|
+
`);
|
|
340
|
+
for (const res of op.requiredResources) {
|
|
341
|
+
leaseStmt.run(res, op.id, domainId, now, op.resourceBudget ? JSON.stringify(op.resourceBudget) : null);
|
|
342
|
+
}
|
|
283
343
|
}
|
|
284
344
|
// 3. 记录领域事件
|
|
285
345
|
this.recordEventAndTransitionState({
|
|
@@ -298,6 +358,39 @@ export class SqliteStore {
|
|
|
298
358
|
});
|
|
299
359
|
});
|
|
300
360
|
}
|
|
361
|
+
recordPreIntentCancelledOperation(op, domainId, result) {
|
|
362
|
+
const run = this.getRun(op.runId);
|
|
363
|
+
if (!run) {
|
|
364
|
+
throw new Error(`Run "${op.runId}" is not registered in domain "${domainId}". ` +
|
|
365
|
+
'Register the task and run first (store.saveTask() + store.saveRun()), then execute operations for that run.');
|
|
366
|
+
}
|
|
367
|
+
if (run.status === 'succeeded' ||
|
|
368
|
+
run.status === 'failed' ||
|
|
369
|
+
run.status === 'cancelled' ||
|
|
370
|
+
run.status === 'indeterminate') {
|
|
371
|
+
throw new Error(`Cannot register operation "${op.id}" for Run "${op.runId}" because the Run is already finalized with status "${run.status}".`);
|
|
372
|
+
}
|
|
373
|
+
this.transaction(() => {
|
|
374
|
+
this.verifyEpochFencing(domainId);
|
|
375
|
+
const existing = this.getOperation(op.id);
|
|
376
|
+
if (existing) {
|
|
377
|
+
throw new Error(`Operation "${op.id}" already exists`);
|
|
378
|
+
}
|
|
379
|
+
const opStmt = this.db.prepare(`
|
|
380
|
+
INSERT INTO operations (id, run_id, domain_id, kind, name, input_fingerprint, status, required_resources, timeout_ms, resource_budget, output_ref, process_identity, result)
|
|
381
|
+
VALUES (?, ?, ?, ?, ?, ?, 'done', ?, ?, ?, ?, ?, ?)
|
|
382
|
+
`);
|
|
383
|
+
opStmt.run(op.id, op.runId, domainId, op.kind, op.name, op.inputFingerprint, JSON.stringify(op.requiredResources || []), op.timeoutMs || null, op.resourceBudget ? JSON.stringify(op.resourceBudget) : null, op.outputRef || null, op.processIdentity ? JSON.stringify(op.processIdentity) : null, JSON.stringify(result));
|
|
384
|
+
this.recordEventAndTransitionState({
|
|
385
|
+
domainId,
|
|
386
|
+
runId: op.runId,
|
|
387
|
+
operationId: op.id,
|
|
388
|
+
type: 'OPERATION_RESULT_RECORDED',
|
|
389
|
+
payload: { result, releaseResources: false },
|
|
390
|
+
timestamp: result.completedAt || new Date().toISOString(),
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
301
394
|
updateOperationStatus(opId, status, processIdentity, outputRef) {
|
|
302
395
|
this.transaction(() => {
|
|
303
396
|
const op = this.getOperation(opId);
|
|
@@ -322,6 +415,20 @@ export class SqliteStore {
|
|
|
322
415
|
}
|
|
323
416
|
});
|
|
324
417
|
}
|
|
418
|
+
updateOperationResult(opId, result) {
|
|
419
|
+
this.transaction(() => {
|
|
420
|
+
const op = this.getOperation(opId);
|
|
421
|
+
if (!op)
|
|
422
|
+
throw new Error(`Operation ${opId} not found`);
|
|
423
|
+
this.verifyEpochFencing(op.domainId);
|
|
424
|
+
const stmt = this.db.prepare(`
|
|
425
|
+
UPDATE operations
|
|
426
|
+
SET result = ?
|
|
427
|
+
WHERE id = ?
|
|
428
|
+
`);
|
|
429
|
+
stmt.run(JSON.stringify(result), opId);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
325
432
|
recordOperationResult(opId, result, releaseResources = true) {
|
|
326
433
|
this.transaction(() => {
|
|
327
434
|
const op = this.getOperation(opId);
|
|
@@ -329,6 +436,9 @@ export class SqliteStore {
|
|
|
329
436
|
throw new Error(`Operation ${opId} not found`);
|
|
330
437
|
}
|
|
331
438
|
this.verifyEpochFencing(op.domainId);
|
|
439
|
+
if (op.status === 'done') {
|
|
440
|
+
throw new Error(`Cannot record result for operation "${opId}": operation is already finalized with status "done"`);
|
|
441
|
+
}
|
|
332
442
|
const stmt = this.db.prepare(`
|
|
333
443
|
UPDATE operations
|
|
334
444
|
SET status = 'done', result = ?, output_ref = COALESCE(?, output_ref)
|
|
@@ -413,6 +523,28 @@ export class SqliteStore {
|
|
|
413
523
|
result: row.result ? JSON.parse(row.result) : undefined,
|
|
414
524
|
}));
|
|
415
525
|
}
|
|
526
|
+
getAllOperations(domainId) {
|
|
527
|
+
const stmt = this.db.prepare(`
|
|
528
|
+
SELECT * FROM operations
|
|
529
|
+
WHERE domain_id = ?
|
|
530
|
+
`);
|
|
531
|
+
const rows = stmt.all(domainId);
|
|
532
|
+
return rows.map((row) => ({
|
|
533
|
+
id: row.id,
|
|
534
|
+
runId: row.run_id,
|
|
535
|
+
domainId: row.domain_id,
|
|
536
|
+
kind: row.kind,
|
|
537
|
+
name: row.name,
|
|
538
|
+
inputFingerprint: row.input_fingerprint,
|
|
539
|
+
requiredResources: JSON.parse(row.required_resources),
|
|
540
|
+
timeoutMs: row.timeout_ms || undefined,
|
|
541
|
+
resourceBudget: row.resource_budget ? JSON.parse(row.resource_budget) : undefined,
|
|
542
|
+
outputRef: row.output_ref || undefined,
|
|
543
|
+
status: row.status,
|
|
544
|
+
processIdentity: row.process_identity ? JSON.parse(row.process_identity) : undefined,
|
|
545
|
+
result: row.result ? JSON.parse(row.result) : undefined,
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
416
548
|
getPersistedResourceLeases(domainId) {
|
|
417
549
|
const stmt = this.db.prepare('SELECT * FROM resource_leases WHERE domain_id = ?');
|
|
418
550
|
const rows = stmt.all(domainId);
|
|
@@ -435,6 +567,7 @@ export class SqliteStore {
|
|
|
435
567
|
}
|
|
436
568
|
}
|
|
437
569
|
recordEventAndTransitionState(event) {
|
|
570
|
+
this.verifyEpochFencing(event.domainId);
|
|
438
571
|
const stmt = this.db.prepare(`
|
|
439
572
|
INSERT INTO journal_events (domain_id, run_id, operation_id, type, payload, timestamp)
|
|
440
573
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -459,6 +592,23 @@ export class SqliteStore {
|
|
|
459
592
|
timestamp: row.timestamp,
|
|
460
593
|
}));
|
|
461
594
|
}
|
|
595
|
+
getEventsByRun(runId) {
|
|
596
|
+
const stmt = this.db.prepare(`
|
|
597
|
+
SELECT * FROM journal_events
|
|
598
|
+
WHERE run_id = ?
|
|
599
|
+
ORDER BY seq ASC
|
|
600
|
+
`);
|
|
601
|
+
const rows = stmt.all(runId);
|
|
602
|
+
return rows.map((row) => ({
|
|
603
|
+
seq: row.seq,
|
|
604
|
+
domainId: row.domain_id,
|
|
605
|
+
runId: row.run_id || undefined,
|
|
606
|
+
operationId: row.operation_id || undefined,
|
|
607
|
+
type: row.type,
|
|
608
|
+
payload: JSON.parse(row.payload),
|
|
609
|
+
timestamp: row.timestamp,
|
|
610
|
+
}));
|
|
611
|
+
}
|
|
462
612
|
close() {
|
|
463
613
|
this.db.close();
|
|
464
614
|
}
|