agentic-workflow-manager 9.12.0 → 9.12.2
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/dist/src/commands/job/gate.js +6 -1
- package/dist/src/commands/job/index.js +22 -1
- package/dist/src/commands/watch/admission-recovery.js +161 -0
- package/dist/src/commands/watch/apply.js +7 -1
- package/dist/src/commands/watch/archive-unused.js +2 -0
- package/dist/src/commands/watch/index.js +55 -0
- package/dist/src/commands/watch/init.js +2 -1
- package/dist/src/commands/watch/request-recovery.js +153 -0
- package/dist/src/commands/watch/supervisor.js +105 -46
- package/dist/src/core/journal/requests.js +1 -0
- package/dist/src/core/journal/types.js +67 -2
- package/dist/tests/commands/watch/admission-recovery.test.js +278 -0
- package/dist/tests/commands/watch/request-recovery.test.js +253 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +88 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Supervisor = exports.MAX_SENSOR_DEFERRALS = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
|
|
3
|
+
exports.Supervisor = exports.ADMISSION_RETRY_DELAY_MS = exports.MAX_SENSOR_DEFERRALS = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
|
|
4
4
|
exports.routingFacts = routingFacts;
|
|
5
5
|
exports.defaultDispatchAdmission = defaultDispatchAdmission;
|
|
6
6
|
exports.admissionForConfig = admissionForConfig;
|
|
7
|
+
exports.recoveryWhitelistBlocker = recoveryWhitelistBlocker;
|
|
7
8
|
exports.runSupervisorLoop = runSupervisorLoop;
|
|
8
9
|
// Loop foreground (R4.4/R4.5): tick = apply -> collect/spawn -> stall -> gate.
|
|
9
10
|
// COMPLETE exige gate verde (que exige cero vivos): drenaje ANTES de declarar.
|
|
@@ -27,6 +28,7 @@ const apply_1 = require("./apply");
|
|
|
27
28
|
const runner_1 = require("./runner");
|
|
28
29
|
const tracks_1 = require("./tracks");
|
|
29
30
|
const generations_1 = require("./generations");
|
|
31
|
+
const types_1 = require("../../core/journal/types");
|
|
30
32
|
/** #166: compact v2 admission requires the runtime identity, and the supervisor
|
|
31
33
|
* never supplied it — so `awm watch` blocked on every tick with
|
|
32
34
|
* ADMISSION_ROUTING_FACTS_REQUIRED even with every other gate green.
|
|
@@ -190,6 +192,21 @@ exports.DEFAULT_SUPERVISOR_CONFIG = {
|
|
|
190
192
|
function admissionForConfig(repoRoot, branch, cfg, deps = {}) {
|
|
191
193
|
return defaultDispatchAdmission(repoRoot, branch, cfg.provider, cfg.routingIdentity, deps, cfg.controllerAutonomy);
|
|
192
194
|
}
|
|
195
|
+
function admissionContextForConfig(cfg) {
|
|
196
|
+
if (!(0, adapter_1.isWatchProvider)(cfg.provider))
|
|
197
|
+
throw new Error('provider de supervisor invalido');
|
|
198
|
+
if (cfg.controllerAutonomy !== undefined && !(0, adapter_1.isControllerAutonomy)(cfg.controllerAutonomy))
|
|
199
|
+
throw new Error('postura del controller invalida');
|
|
200
|
+
const runtime = cfg.routingIdentity === undefined ? undefined : (0, capabilities_1.validateRuntimeKey)({ target: cfg.provider, ...cfg.routingIdentity });
|
|
201
|
+
return { provider: cfg.provider,
|
|
202
|
+
...(runtime ? { runtime: { kind: runtime.kind, version: runtime.version, accountScopeDigest: runtime.accountScopeDigest } } : {}),
|
|
203
|
+
...(cfg.controllerAutonomy ? { controllerAutonomy: cfg.controllerAutonomy } : {}) };
|
|
204
|
+
}
|
|
205
|
+
function sameAdmissionContext(left, right) {
|
|
206
|
+
return left.provider === right.provider && left.controllerAutonomy === right.controllerAutonomy
|
|
207
|
+
&& left.runtime?.kind === right.runtime?.kind && left.runtime?.version === right.runtime?.version
|
|
208
|
+
&& left.runtime?.accountScopeDigest === right.runtime?.accountScopeDigest;
|
|
209
|
+
}
|
|
193
210
|
// 'frozen' (R5.2/R6.3, Task 10): SOLO puede ocurrir en el journal de un
|
|
194
211
|
// TRACK individual (nunca en el del plan) — el track terminó, con las 6
|
|
195
212
|
// observaciones demostrables (cero jobs vivos, gate local verde, worktree
|
|
@@ -198,12 +215,31 @@ function admissionForConfig(repoRoot, branch, cfg, deps = {}) {
|
|
|
198
215
|
/** How many consecutive ticks an inconclusive sensor verdict may defer dispatch
|
|
199
216
|
* before the supervisor says so out loud. Small: the deferral covers a verdict
|
|
200
217
|
* still settling, not one that never will. */
|
|
201
|
-
exports.MAX_SENSOR_DEFERRALS =
|
|
218
|
+
exports.MAX_SENSOR_DEFERRALS = types_1.MAX_ADMISSION_DEFERRALS;
|
|
219
|
+
exports.ADMISSION_RETRY_DELAY_MS = 5000;
|
|
220
|
+
function transientAdmission(report) {
|
|
221
|
+
// Currentness and sensors are evaluated before journalStatus, so their
|
|
222
|
+
// blocked reports legitimately carry journal=not-required. The caller
|
|
223
|
+
// verifies the bound journal identity independently before deferring.
|
|
224
|
+
if (report.state !== 'blocked' || report.executionMode !== 'desatendido'
|
|
225
|
+
|| report.planState !== 'valid' || !['not-required', 'current'].includes(report.journal)
|
|
226
|
+
|| report.diagnostics.length !== 1)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (report.currentness === 'unverifiable' && report.sensors === 'not-required'
|
|
229
|
+
&& report.diagnostics[0].code === 'ADMISSION_CURRENTNESS_BLOCKED')
|
|
230
|
+
return 'currentness';
|
|
231
|
+
if (report.currentness === 'current' && report.sensors === 'not-certified'
|
|
232
|
+
&& report.diagnostics[0].code === 'ADMISSION_SENSORS_BLOCKED')
|
|
233
|
+
return 'sensors';
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
202
236
|
const LIVE = ['received', 'spawn-intent', 'claimed', 'running', 'cancel-requested'];
|
|
203
237
|
function recoveryWhitelistBlocker(state) {
|
|
204
|
-
if (state.cycle.status === 'BLOCKED')
|
|
205
|
-
return '
|
|
206
|
-
|
|
238
|
+
if (state.cycle.status === 'BLOCKED') {
|
|
239
|
+
return state.cycle.blockedReason === 'recovery no autorizado: hay conflictos durables de requests'
|
|
240
|
+
? 'hay conflictos durables de requests' : 'el ciclo está bloqueado';
|
|
241
|
+
}
|
|
242
|
+
if ((0, types_1.activeRequestProblems)(state).length > 0)
|
|
207
243
|
return 'hay conflictos durables de requests';
|
|
208
244
|
// Pending work is the normal reason to launch (or retry launching) the
|
|
209
245
|
// controller. It is not recovery evidence and therefore cannot turn a
|
|
@@ -226,21 +262,19 @@ class Supervisor {
|
|
|
226
262
|
cfg;
|
|
227
263
|
spawner;
|
|
228
264
|
dispatchAdmission;
|
|
265
|
+
autoBeginGeneration;
|
|
229
266
|
backoff = new generations_1.Backoff();
|
|
230
267
|
relaunchNotBefore = 0;
|
|
231
|
-
/** Consecutive ticks deferred on an inconclusive sensor verdict. Bounded:
|
|
232
|
-
* the deferral exists for a verdict that is still settling, and a verdict
|
|
233
|
-
* that never settles must become visible rather than loop forever. */
|
|
234
|
-
sensorDeferrals = 0;
|
|
235
268
|
lastActivity = null;
|
|
236
269
|
lastGenerationToken = null;
|
|
237
270
|
trackRuntime;
|
|
238
|
-
constructor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission = admissionForConfig(repoRoot, branch, cfg)) {
|
|
271
|
+
constructor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission = admissionForConfig(repoRoot, branch, cfg), autoBeginGeneration = false) {
|
|
239
272
|
this.repoRoot = repoRoot;
|
|
240
273
|
this.branch = branch;
|
|
241
274
|
this.cfg = cfg;
|
|
242
275
|
this.spawner = spawner;
|
|
243
276
|
this.dispatchAdmission = dispatchAdmission;
|
|
277
|
+
this.autoBeginGeneration = autoBeginGeneration;
|
|
244
278
|
this.trackRuntime = trackRuntime ?? (0, tracks_1.defaultTrackRuntime)(repoRoot, branch, { termGraceMs: cfg.termGraceMs, killGraceMs: cfg.killGraceMs });
|
|
245
279
|
}
|
|
246
280
|
fingerprintNow = (argv, paths, cwd) => {
|
|
@@ -277,7 +311,7 @@ class Supervisor {
|
|
|
277
311
|
}
|
|
278
312
|
}
|
|
279
313
|
async tick() {
|
|
280
|
-
|
|
314
|
+
let before0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
281
315
|
if (before0.corrupt || before0.state === null)
|
|
282
316
|
throw new Error('journal corrupto: el supervisor no opera sobre corrupcion (R1.6)');
|
|
283
317
|
// Historical/schema-1 journals are intentionally readable by migration
|
|
@@ -291,6 +325,24 @@ class Supervisor {
|
|
|
291
325
|
// longer reconstructible.
|
|
292
326
|
if (before0.state.cycle.status === 'COMPLETE')
|
|
293
327
|
return 'complete';
|
|
328
|
+
// A historical custody cannot be silently converted into another
|
|
329
|
+
// cause on a later tick. Only the locked offline recovery may resume.
|
|
330
|
+
if (before0.state.cycle.status === 'BLOCKED')
|
|
331
|
+
return 'custody';
|
|
332
|
+
const assertedContext = admissionContextForConfig(this.cfg);
|
|
333
|
+
if (before0.state.admissionContext && !sameAdmissionContext(before0.state.admissionContext, assertedContext)) {
|
|
334
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'identidad de admisión cambió al reiniciar: provider/runtime/postura');
|
|
335
|
+
return 'custody';
|
|
336
|
+
}
|
|
337
|
+
if (!before0.state.admissionContext && assertedContext.runtime && assertedContext.controllerAutonomy) {
|
|
338
|
+
before0.state.admissionContext = assertedContext;
|
|
339
|
+
(0, store_1.writeJournal)(this.repoRoot, this.branch, before0.state);
|
|
340
|
+
before0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
341
|
+
if (before0.corrupt || !before0.state)
|
|
342
|
+
throw new Error('journal no verificable tras vincular identidad de admisión');
|
|
343
|
+
}
|
|
344
|
+
if (before0.state.admissionRetry && Date.now() < Date.parse(before0.state.admissionRetry.nextRetryAt))
|
|
345
|
+
return 'continue';
|
|
294
346
|
// A controller can create jobs and runnerTick can start them. Both are
|
|
295
347
|
// downstream of the same full compact unattended admission, so it must
|
|
296
348
|
// complete before any reconciliation path that could dispatch either.
|
|
@@ -305,40 +357,49 @@ class Supervisor {
|
|
|
305
357
|
}
|
|
306
358
|
if (admission.state !== 'admitted' || admission.executionMode !== 'desatendido'
|
|
307
359
|
|| admission.currentness !== 'current' || admission.sensors !== 'pass' || admission.journal !== 'current') {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.sensorDeferrals += 1;
|
|
326
|
-
if (this.sensorDeferrals <= exports.MAX_SENSOR_DEFERRALS)
|
|
360
|
+
const kind = transientAdmission(admission);
|
|
361
|
+
if (kind) {
|
|
362
|
+
const binding = before0.state.planBinding;
|
|
363
|
+
const plan = (0, validate_1.validatePlanFile)(binding.path, this.repoRoot);
|
|
364
|
+
if (plan.state !== 'valid' || plan.planDigest !== binding.digest || plan.schema !== binding.schema
|
|
365
|
+
|| (binding.executionDigest !== undefined && plan.executionDigest !== binding.executionDigest)
|
|
366
|
+
|| (admission.planDigest !== undefined && admission.planDigest !== binding.digest)) {
|
|
367
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'binding del plan alterado durante admisión transitoria');
|
|
368
|
+
return 'custody';
|
|
369
|
+
}
|
|
370
|
+
const prior = before0.state.admissionRetry;
|
|
371
|
+
const attempts = (prior?.attempts ?? 0) + 1;
|
|
372
|
+
if (attempts <= types_1.MAX_ADMISSION_DEFERRALS) {
|
|
373
|
+
const now = Date.now();
|
|
374
|
+
before0.state.admissionRetry = { kind, attempts, firstAt: prior?.firstAt ?? new Date(now).toISOString(), nextRetryAt: new Date(now + exports.ADMISSION_RETRY_DELAY_MS).toISOString() };
|
|
375
|
+
(0, store_1.writeJournal)(this.repoRoot, this.branch, before0.state);
|
|
376
|
+
(0, store_1.appendEvent)(this.repoRoot, this.branch, { kind: 'admission-retry', cause: kind, attempts, retryAfterMs: exports.ADMISSION_RETRY_DELAY_MS });
|
|
327
377
|
return 'continue';
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// tick defeats that guard and rewrites the journal plus appends a
|
|
331
|
-
// custody-blocked event every tickMs, without bound. Naming the
|
|
332
|
-
// bound instead of the count keeps the reason stable, so custody
|
|
333
|
-
// is entered once and stays entered.
|
|
334
|
-
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `veredicto de sensores no concluyente tras ${exports.MAX_SENSOR_DEFERRALS} ticks: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
|
|
378
|
+
}
|
|
379
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión ${kind} no verificable tras ${types_1.MAX_ADMISSION_DEFERRALS} reintentos: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
|
|
335
380
|
return 'custody';
|
|
336
381
|
}
|
|
337
382
|
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión compacta desatendida bloqueada antes de dispatch: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
|
|
338
383
|
return 'custody';
|
|
339
384
|
}
|
|
340
|
-
//
|
|
341
|
-
|
|
385
|
+
// An incomplete assertion can be repaired while the cycle is
|
|
386
|
+
// still waiting. Bind it only after an admitted observation, never
|
|
387
|
+
// on an early currentness/sensor response that has not reached the
|
|
388
|
+
// provider, runtime and autonomy gates yet.
|
|
389
|
+
if (!before0.state.admissionContext) {
|
|
390
|
+
before0.state.admissionContext = assertedContext;
|
|
391
|
+
(0, store_1.writeJournal)(this.repoRoot, this.branch, before0.state);
|
|
392
|
+
before0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
393
|
+
if (before0.corrupt || !before0.state)
|
|
394
|
+
throw new Error('journal no verificable tras vincular admisión completa');
|
|
395
|
+
}
|
|
396
|
+
if (before0.state.admissionRetry) {
|
|
397
|
+
before0.state.admissionRetry = undefined;
|
|
398
|
+
(0, store_1.writeJournal)(this.repoRoot, this.branch, before0.state);
|
|
399
|
+
before0 = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
400
|
+
if (before0.corrupt || !before0.state)
|
|
401
|
+
throw new Error('journal no verificable tras resolver reintento de admisión');
|
|
402
|
+
}
|
|
342
403
|
}
|
|
343
404
|
let recoveryResumeAction;
|
|
344
405
|
// Schema-2 custody is reconciled before any controller launch. This is
|
|
@@ -392,14 +453,14 @@ class Supervisor {
|
|
|
392
453
|
// verdicts, and an adverse verdict without a closed fix all stop
|
|
393
454
|
// recovery before a controller can create more work.
|
|
394
455
|
const verdictById = new Map(before0.state.verdicts.map(verdict => [verdict.id, verdict]));
|
|
395
|
-
const openReviewOrFix = before0.state.tasks.some(task => {
|
|
456
|
+
const openReviewOrFix = (terminalTaskClaims && before0.state.tasks.some(task => {
|
|
396
457
|
const obligations = task.reviewObligations;
|
|
397
458
|
return !['spec', 'quality'].every(kind => obligations.some(obligation => obligation.kind === kind))
|
|
398
459
|
|| obligations.some(obligation => {
|
|
399
460
|
const verdict = obligation.verdictId === undefined ? undefined : verdictById.get(obligation.verdictId);
|
|
400
461
|
return verdict === undefined || verdict.result !== 'pass';
|
|
401
462
|
});
|
|
402
|
-
}) || before0.state.verdicts.some(verdict => verdict.result !== 'pass'
|
|
463
|
+
})) || before0.state.verdicts.some(verdict => verdict.result !== 'pass'
|
|
403
464
|
&& !before0.state.fixes.some(fix => fix.verdictId === verdict.id && fix.closed));
|
|
404
465
|
const recovery = (0, fingerprint_1.reconcileUnattendedRecovery)({
|
|
405
466
|
journal: before0.state, journalCorrupt: false, plan: before0.state.planBinding,
|
|
@@ -438,6 +499,8 @@ class Supervisor {
|
|
|
438
499
|
// mismo resultado terminal.
|
|
439
500
|
if (before.frozen !== undefined)
|
|
440
501
|
return 'frozen';
|
|
502
|
+
if (this.autoBeginGeneration && (0, generations_1.activeGeneration)(before) === undefined)
|
|
503
|
+
(0, generations_1.beginGeneration)(this.repoRoot, this.branch);
|
|
441
504
|
const pending = before.cycle.nextAction;
|
|
442
505
|
const resumeAction = recoveryResumeAction ?? { schema: 'controller-recovery/v1', kind: pending !== undefined ? 'resume-next-action' : 'resume-cycle' };
|
|
443
506
|
if (this.ensureController(resumeAction) === 'custody')
|
|
@@ -717,12 +780,8 @@ async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.d
|
|
|
717
780
|
const onSignal = () => { shutdownRequested = true; wakeSleep?.(); };
|
|
718
781
|
process.on('SIGINT', onSignal);
|
|
719
782
|
process.on('SIGTERM', onSignal);
|
|
720
|
-
const sup = new Supervisor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission);
|
|
783
|
+
const sup = new Supervisor(repoRoot, branch, cfg, spawner, trackRuntime, dispatchAdmission, true);
|
|
721
784
|
try {
|
|
722
|
-
const s0 = (0, store_1.readJournal)(repoRoot, branch).state;
|
|
723
|
-
if ((0, generations_1.activeGeneration)(s0) === undefined) {
|
|
724
|
-
(0, generations_1.beginGeneration)(repoRoot, branch);
|
|
725
|
-
}
|
|
726
785
|
for (;;) {
|
|
727
786
|
if (shutdownRequested)
|
|
728
787
|
break;
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.digestOf = digestOf;
|
|
7
7
|
exports.emitRequest = emitRequest;
|
|
8
|
+
exports.isWellFormedEnvelope = isWellFormedEnvelope;
|
|
8
9
|
exports.listPendingRequests = listPendingRequests;
|
|
9
10
|
exports.applyOutcome = applyOutcome;
|
|
10
11
|
exports.ackFor = ackFor;
|
|
@@ -5,7 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
6
|
};
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.GENERATION_STATES = exports.EXECUTION_STATES = void 0;
|
|
8
|
+
exports.MAX_ADMISSION_DEFERRALS = exports.GENERATION_STATES = exports.EXECUTION_STATES = void 0;
|
|
9
|
+
exports.activeRequestProblems = activeRequestProblems;
|
|
9
10
|
exports.isControllerRecoveryAction = isControllerRecoveryAction;
|
|
10
11
|
exports.emptyState = emptyState;
|
|
11
12
|
exports.isWellFormedState = isWellFormedState;
|
|
@@ -14,6 +15,7 @@ exports.isRoutingEnvelope = isRoutingEnvelope;
|
|
|
14
15
|
exports.isWellFormedProcessRef = isWellFormedProcessRef;
|
|
15
16
|
exports.isWellFormedJob = isWellFormedJob;
|
|
16
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
17
19
|
const types_1 = require("../tracks/types");
|
|
18
20
|
const paths_1 = require("./paths");
|
|
19
21
|
exports.EXECUTION_STATES = [
|
|
@@ -35,6 +37,20 @@ const COHORT_PHASES = [
|
|
|
35
37
|
];
|
|
36
38
|
const _cohortPhasesComplete = true;
|
|
37
39
|
const VERIFICATION_KINDS = ['test', 'lint', 'sensors', 'review', 'qa', 'interlock', 'track-integration'];
|
|
40
|
+
function activeRequestProblems(state) {
|
|
41
|
+
return state.requestProblems.filter(problem => {
|
|
42
|
+
const resolution = problem.resolution;
|
|
43
|
+
if (!resolution || !problem.requestId || !problem.taskId || !problem.generationToken
|
|
44
|
+
|| path_1.default.basename(problem.file) !== `${problem.requestId}.json`
|
|
45
|
+
|| resolution.generationToken !== problem.generationToken)
|
|
46
|
+
return true;
|
|
47
|
+
const applied = state.appliedRequests[resolution.replacementRequestId];
|
|
48
|
+
return applied?.outcome !== 'applied' || applied.resultRef !== problem.taskId
|
|
49
|
+
|| applied.payloadDigest !== resolution.replacementPayloadDigest
|
|
50
|
+
|| !state.tasks.some(task => task.id === problem.taskId);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
exports.MAX_ADMISSION_DEFERRALS = 3;
|
|
38
54
|
function isControllerRecoveryAction(x) {
|
|
39
55
|
return isObj(x) && Object.keys(x).length === 2
|
|
40
56
|
&& x.schema === 'controller-recovery/v1'
|
|
@@ -102,6 +118,12 @@ function isWellFormedState(x) {
|
|
|
102
118
|
return false;
|
|
103
119
|
if (x.custodyDecisions !== undefined && (!Array.isArray(x.custodyDecisions) || !x.custodyDecisions.every(isWellFormedCustodyDecision)))
|
|
104
120
|
return false;
|
|
121
|
+
if (x.admissionRetry !== undefined && !isWellFormedAdmissionRetry(x.admissionRetry))
|
|
122
|
+
return false;
|
|
123
|
+
if (x.admissionContext !== undefined && !isWellFormedAdmissionContext(x.admissionContext))
|
|
124
|
+
return false;
|
|
125
|
+
if (x.admissionRecoveries !== undefined && (!Array.isArray(x.admissionRecoveries) || !x.admissionRecoveries.every(isWellFormedAdmissionRecovery)))
|
|
126
|
+
return false;
|
|
105
127
|
if (!x.generations.every(isWellFormedGeneration) || !x.tasks.every(isWellFormedTask))
|
|
106
128
|
return false;
|
|
107
129
|
if (!x.cycleVerificationPlan.every(isWellFormedVerificationItem))
|
|
@@ -240,12 +262,55 @@ function isRoutingEnvelope(x) { if (!isObj(x) || !Object.keys(x).every(key => ['
|
|
|
240
262
|
function isWellFormedImplementationLineage(x) { return isObj(x) && Object.keys(x).every(key => ['id', 'obligationId', 'sliceId', 'planDigest', 'executionDigest', 'initialProfile', 'initialEffort', 'attempts'].includes(key)) && typeof x.id === 'string' && x.id.length > 0 && typeof x.obligationId === 'string' && x.obligationId.length > 0 && typeof x.sliceId === 'string' && x.sliceId.length > 0 && isDigest(x.planDigest) && isDigest(x.executionDigest) && ['mechanical', 'integration', 'judgment'].includes(String(x.initialProfile)) && ['medium', 'high', 'runtime-default'].includes(String(x.initialEffort)) && Number.isSafeInteger(x.attempts) && x.attempts >= 0 && x.attempts <= 3; }
|
|
241
263
|
function isWellFormedRequestProblem(x) {
|
|
242
264
|
return isObj(x) && typeof x.file === 'string' && (x.kind === 'corrupt' || x.kind === 'rejected')
|
|
243
|
-
&& typeof x.detail === 'string' && typeof x.at === 'string'
|
|
265
|
+
&& typeof x.detail === 'string' && typeof x.at === 'string'
|
|
266
|
+
&& (x.requestId === undefined || typeof x.requestId === 'string')
|
|
267
|
+
&& (x.taskId === undefined || typeof x.taskId === 'string')
|
|
268
|
+
&& (x.generationToken === undefined || typeof x.generationToken === 'string')
|
|
269
|
+
&& (x.resolution === undefined || (x.kind === 'rejected' && isObj(x.resolution)
|
|
270
|
+
&& Object.keys(x.resolution).every(key => ['replacementRequestId', 'replacementPayloadDigest', 'reason', 'generationToken', 'at'].includes(key))
|
|
271
|
+
&& typeof x.requestId === 'string' && x.requestId.length > 0
|
|
272
|
+
&& typeof x.taskId === 'string' && x.taskId.length > 0
|
|
273
|
+
&& typeof x.generationToken === 'string' && x.generationToken.length > 0
|
|
274
|
+
&& typeof x.resolution.replacementRequestId === 'string' && x.resolution.replacementRequestId.length > 0
|
|
275
|
+
&& typeof x.resolution.replacementPayloadDigest === 'string' && /^[a-f0-9]{64}$/.test(x.resolution.replacementPayloadDigest)
|
|
276
|
+
&& typeof x.resolution.reason === 'string' && x.resolution.reason.trim().length > 0
|
|
277
|
+
&& typeof x.resolution.generationToken === 'string' && x.resolution.generationToken.length > 0
|
|
278
|
+
&& typeof x.resolution.at === 'string' && Number.isFinite(Date.parse(x.resolution.at))));
|
|
244
279
|
}
|
|
245
280
|
function isWellFormedCustodyDecision(x) {
|
|
246
281
|
return isObj(x) && typeof x.at === 'string' && x.decision === 'resume'
|
|
247
282
|
&& typeof x.reason === 'string' && typeof x.generationToken === 'string';
|
|
248
283
|
}
|
|
284
|
+
function isWellFormedAdmissionRetry(x) {
|
|
285
|
+
return isObj(x) && Object.keys(x).length === 4
|
|
286
|
+
&& (x.kind === 'currentness' || x.kind === 'sensors')
|
|
287
|
+
&& Number.isInteger(x.attempts) && Number(x.attempts) >= 1 && Number(x.attempts) <= exports.MAX_ADMISSION_DEFERRALS
|
|
288
|
+
&& typeof x.firstAt === 'string' && Number.isFinite(Date.parse(x.firstAt))
|
|
289
|
+
&& typeof x.nextRetryAt === 'string' && Number.isFinite(Date.parse(x.nextRetryAt))
|
|
290
|
+
&& Date.parse(x.nextRetryAt) >= Date.parse(x.firstAt);
|
|
291
|
+
}
|
|
292
|
+
function isWellFormedAdmissionContext(x) {
|
|
293
|
+
if (!isObj(x) || !Object.keys(x).every(key => ['provider', 'runtime', 'controllerAutonomy'].includes(key))
|
|
294
|
+
|| (x.provider !== 'codex' && x.provider !== 'claude-code')
|
|
295
|
+
|| (x.controllerAutonomy !== undefined && x.controllerAutonomy !== 'approval-free'))
|
|
296
|
+
return false;
|
|
297
|
+
if (x.runtime === undefined)
|
|
298
|
+
return true;
|
|
299
|
+
return isObj(x.runtime) && Object.keys(x.runtime).length === 3
|
|
300
|
+
&& typeof x.runtime.kind === 'string' && x.runtime.kind.length > 0
|
|
301
|
+
&& typeof x.runtime.version === 'string' && x.runtime.version.length > 0
|
|
302
|
+
&& typeof x.runtime.accountScopeDigest === 'string' && /^[a-f0-9]{64}$/.test(x.runtime.accountScopeDigest);
|
|
303
|
+
}
|
|
304
|
+
function isWellFormedAdmissionRecovery(x) {
|
|
305
|
+
return isObj(x) && Object.keys(x).every(key => ['at', 'reason', 'blockedReason', 'generationToken', 'context', 'planDigest', 'assertion'].includes(key))
|
|
306
|
+
&& typeof x.at === 'string' && Number.isFinite(Date.parse(x.at))
|
|
307
|
+
&& typeof x.reason === 'string' && x.reason.trim().length > 0 && x.reason.length <= 1024
|
|
308
|
+
&& typeof x.blockedReason === 'string' && x.blockedReason.length > 0
|
|
309
|
+
&& (x.generationToken === undefined || (typeof x.generationToken === 'string' && x.generationToken.length > 0))
|
|
310
|
+
&& isWellFormedAdmissionContext(x.context)
|
|
311
|
+
&& typeof x.planDigest === 'string' && /^[a-f0-9]{64}$/.test(x.planDigest)
|
|
312
|
+
&& (x.assertion === 'recorded' || x.assertion === 'legacy-operator-asserted');
|
|
313
|
+
}
|
|
249
314
|
function isWellFormedProcessRef(x) {
|
|
250
315
|
if (!isObj(x))
|
|
251
316
|
return false;
|