agentic-workflow-manager 9.12.2 → 9.12.4
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/index.js +61 -0
- package/dist/src/commands/watch/admission-recovery.js +13 -2
- package/dist/src/commands/watch/apply.js +118 -2
- package/dist/src/commands/watch/generations.js +1 -1
- package/dist/src/commands/watch/index.js +12 -2
- package/dist/src/commands/watch/supervisor.js +114 -24
- package/dist/src/core/admission/index.js +2 -0
- package/dist/src/core/admission/registry-contracts.js +74 -3
- package/dist/src/core/journal/requests.js +6 -0
- package/dist/src/core/journal/types.js +40 -3
- package/dist/tests/commands/watch/admission-recovery.test.js +44 -0
- package/dist/tests/commands/watch/plan-lifecycle.test.js +47 -1
- package/dist/tests/commands/watch/reopened-196.test.js +1042 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +23 -2
- 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
|
@@ -19,6 +19,10 @@ const versioning_1 = require("../versioning");
|
|
|
19
19
|
const check_1 = require("../currentness/check");
|
|
20
20
|
const run_1 = require("../../commands/sensors/run");
|
|
21
21
|
const index_1 = require("./index");
|
|
22
|
+
const store_1 = require("../journal/store");
|
|
23
|
+
const paths_1 = require("../journal/paths");
|
|
24
|
+
const generations_1 = require("../../commands/watch/generations");
|
|
25
|
+
const process_1 = require("../journal/process");
|
|
22
26
|
const FRAMEWORK_CONTRACTS = ['using-awm', 'writing-plans', 'development-process', 'subagent-driven-development', 'executing-plans', 'post-implementation-qa', 'post-implementation-docs', 'harness-retro', 'finishing-a-development-branch'];
|
|
23
27
|
const MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
24
28
|
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
@@ -217,6 +221,56 @@ function registryCompatibility(registries, consumed) {
|
|
|
217
221
|
}
|
|
218
222
|
return diagnostics;
|
|
219
223
|
}
|
|
224
|
+
/** A controller may ask for admission while its supervisor is writing journal
|
|
225
|
+
* events. The already-launched generation passed sensors before launch; its
|
|
226
|
+
* durable intent plus verified live process identity is the evidence, not a
|
|
227
|
+
* second tree snapshot. A claim alone never certifies process ownership. */
|
|
228
|
+
function hasOwnedGenerationAdmission(input) {
|
|
229
|
+
if (!input.verifySensors || input.freshSensorsRequired || input.executionMode !== 'desatendido' || input.plan.state !== 'valid'
|
|
230
|
+
|| input.journalCorrupt || !input.journalState || input.journalState.schema !== 2
|
|
231
|
+
|| input.journalState.cycle.status !== 'IN_PROGRESS'
|
|
232
|
+
|| input.journalState.admissionContext?.provider !== input.provider
|
|
233
|
+
|| input.journalState.admissionContext.controllerAutonomy !== input.controllerAutonomy)
|
|
234
|
+
return false;
|
|
235
|
+
const observed = input.journalState;
|
|
236
|
+
const binding = observed.planBinding;
|
|
237
|
+
if (!binding)
|
|
238
|
+
return false;
|
|
239
|
+
if (input.plan.schema === 'compact-slices/v2' && !observed.admissionContext?.runtime)
|
|
240
|
+
return false;
|
|
241
|
+
let expectedPath;
|
|
242
|
+
try {
|
|
243
|
+
expectedPath = (0, paths_1.bindingPlanPath)(input.planPath ?? '');
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (binding.path !== expectedPath || binding.digest !== input.plan.planDigest || binding.schema !== input.plan.schema
|
|
249
|
+
|| (binding.executionDigest !== undefined && binding.executionDigest !== input.plan.executionDigest))
|
|
250
|
+
return false;
|
|
251
|
+
let fresh;
|
|
252
|
+
try {
|
|
253
|
+
fresh = (0, store_1.readJournal)(input.cwd, observed.branch);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (fresh.corrupt || !fresh.state || fresh.state.cycle.status !== 'IN_PROGRESS'
|
|
259
|
+
|| fresh.state.revision < observed.revision || JSON.stringify(fresh.state.planBinding) !== JSON.stringify(binding)
|
|
260
|
+
|| JSON.stringify(fresh.state.admissionContext) !== JSON.stringify(observed.admissionContext))
|
|
261
|
+
return false;
|
|
262
|
+
const generation = (0, generations_1.activeGeneration)(fresh.state);
|
|
263
|
+
if (!generation || generation !== fresh.state.generations.at(-1)
|
|
264
|
+
|| generation.provider !== input.provider || !generation.controllerJobId || !generation.launchArgvDigest)
|
|
265
|
+
return false;
|
|
266
|
+
const prior = observed.generations.find(item => item.token === generation.token);
|
|
267
|
+
if (!prior || prior.controllerJobId !== generation.controllerJobId || prior.launchArgvDigest !== generation.launchArgvDigest)
|
|
268
|
+
return false;
|
|
269
|
+
return (generation.processRef !== undefined && generation.processRef.spawnNonce === generation.spawnNonce
|
|
270
|
+
&& generation.processRef.argvDigest === generation.launchArgvDigest && (0, process_1.refIsAlive)(generation.processRef))
|
|
271
|
+
|| (generation.wrapperRef !== undefined && generation.wrapperRef.spawnNonce === generation.spawnNonce
|
|
272
|
+
&& (0, process_1.refIsAlive)(generation.wrapperRef));
|
|
273
|
+
}
|
|
220
274
|
/** One admission authority for the public command AND actual watch dispatch.
|
|
221
275
|
* No empirical execution occurs before provenance/currentness/CLI-floor gates. */
|
|
222
276
|
async function admitRegistryPlan(input, dependencies = {}) {
|
|
@@ -253,12 +307,14 @@ async function admitRegistryPlan(input, dependencies = {}) {
|
|
|
253
307
|
}
|
|
254
308
|
return scope.provenance === 'proven';
|
|
255
309
|
};
|
|
310
|
+
let generationBound = false;
|
|
256
311
|
const compose = async (fields) => {
|
|
257
312
|
const report = await admission({ ...input, provenance: scope.provenance, consumedRegistryComponents: scope.consumedRegistryComponents, ...fields });
|
|
313
|
+
const withEvidence = generationBound && report.sensors === 'pass' ? { ...report, sensorEvidence: 'generation-bound' } : report;
|
|
258
314
|
if (scope.provenanceDiagnostic && report.diagnostics.some(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED')) {
|
|
259
|
-
return { ...
|
|
315
|
+
return { ...withEvidence, diagnostics: report.diagnostics.map(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED' ? scope.provenanceDiagnostic : diagnostic) };
|
|
260
316
|
}
|
|
261
|
-
return
|
|
317
|
+
return withEvidence;
|
|
262
318
|
};
|
|
263
319
|
let currentness = input.currentness;
|
|
264
320
|
let compatibilityDiagnostics;
|
|
@@ -277,7 +333,10 @@ async function admitRegistryPlan(input, dependencies = {}) {
|
|
|
277
333
|
if (compatibilityGate.currentness !== 'current')
|
|
278
334
|
return compatibilityGate;
|
|
279
335
|
}
|
|
280
|
-
|
|
336
|
+
generationBound = input.sensors === undefined && hasOwnedGenerationAdmission(input);
|
|
337
|
+
const sensors = input.verifySensors ? input.sensors ?? (generationBound
|
|
338
|
+
? { sensors: [], overall: 'pass', reason: 'generation-bound-admission' }
|
|
339
|
+
: await (dependencies.runSensors ?? run_1.runSensors)({ cwd: input.cwd, all: true, readOnly: true })) : undefined;
|
|
281
340
|
if (input.requireCurrent && input.verifySensors) {
|
|
282
341
|
if (!refresh())
|
|
283
342
|
return compose({ currentness, sensors });
|
|
@@ -286,5 +345,17 @@ async function admitRegistryPlan(input, dependencies = {}) {
|
|
|
286
345
|
return currentnessGate;
|
|
287
346
|
compatibilityDiagnostics = registryCompatibility(registries, scope.consumedRegistryComponents);
|
|
288
347
|
}
|
|
348
|
+
if (generationBound && input.plan.schema === 'compact-slices/v2') {
|
|
349
|
+
const routing = input.routing ?? await dependencies.readRouting?.();
|
|
350
|
+
const expected = input.journalState?.admissionContext?.runtime;
|
|
351
|
+
if (!expected || !routing?.runtime || routing.runtime.target !== input.provider
|
|
352
|
+
|| routing.runtime.kind !== expected.kind || routing.runtime.version !== expected.version
|
|
353
|
+
|| routing.runtime.accountScopeDigest !== expected.accountScopeDigest) {
|
|
354
|
+
return compose({ currentness, compatibilityDiagnostics, sensors,
|
|
355
|
+
routing: { ...routing, eventIssue: { code: 'ADMISSION_GENERATION_RUNTIME_MISMATCH',
|
|
356
|
+
message: 'Live runtime identity differs from the admitted controller generation; stop and recover with a fresh admission.' } } });
|
|
357
|
+
}
|
|
358
|
+
return compose({ currentness, compatibilityDiagnostics, sensors, routing });
|
|
359
|
+
}
|
|
289
360
|
return compose({ currentness, compatibilityDiagnostics, sensors, ...(dependencies.readRouting ? { routingReader: dependencies.readRouting } : {}) });
|
|
290
361
|
}
|
|
@@ -85,9 +85,15 @@ function isWellFormedEnvelope(x) {
|
|
|
85
85
|
}
|
|
86
86
|
function listPendingRequests(repoRoot, branch) {
|
|
87
87
|
const dir = (0, paths_1.requestsDir)(repoRoot, branch);
|
|
88
|
+
const directory = fs_1.default.lstatSync(dir);
|
|
89
|
+
if (directory.isSymbolicLink() || !directory.isDirectory())
|
|
90
|
+
throw new Error('request directory is not a controlled directory');
|
|
88
91
|
return fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.json')).sort().map((f) => {
|
|
89
92
|
const file = path_1.default.join(dir, f);
|
|
90
93
|
try {
|
|
94
|
+
const stat = fs_1.default.lstatSync(file);
|
|
95
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
96
|
+
return { requestId: f, envelope: null, file, corrupt: true };
|
|
91
97
|
const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
92
98
|
// Shape completa antes de cualquier uso downstream. JSON valido no
|
|
93
99
|
// implica envelope valido: payload/generation/idempotency ausentes
|
|
@@ -99,6 +99,8 @@ function isWellFormedState(x) {
|
|
|
99
99
|
return false;
|
|
100
100
|
if (!Array.isArray(x.generations) || !Array.isArray(x.tasks))
|
|
101
101
|
return false;
|
|
102
|
+
if (!x.tasks.every(isWellFormedTask))
|
|
103
|
+
return false;
|
|
102
104
|
if (!Array.isArray(x.cycleVerificationPlan) || !Array.isArray(x.verdicts) || !Array.isArray(x.fixes))
|
|
103
105
|
return false;
|
|
104
106
|
if (!Array.isArray(x.requiredVerifiers) || !x.requiredVerifiers.every((kind) => VERIFICATION_KINDS.includes(kind))
|
|
@@ -108,8 +110,40 @@ function isWellFormedState(x) {
|
|
|
108
110
|
return false;
|
|
109
111
|
if (!isObj(x.appliedRequests) || !Object.values(x.appliedRequests).every(isWellFormedAppliedRequest))
|
|
110
112
|
return false;
|
|
113
|
+
if (!Object.entries(x.appliedRequests).every(([requestId, ack]) => requestId === ack.requestId))
|
|
114
|
+
return false;
|
|
111
115
|
if (x.routingAttempts !== undefined && (!Array.isArray(x.routingAttempts) || x.routingAttempts.length > 4096 || !x.routingAttempts.every(isWellFormedRoutingAttempt)))
|
|
112
116
|
return false;
|
|
117
|
+
// A linked v2 dispatch is evidence of a native handoff. Reject a broken
|
|
118
|
+
// link or a second dispatch for one reservation on journal read as well as
|
|
119
|
+
// at request-application time. Historical unlinked records remain readable
|
|
120
|
+
// but cannot masquerade as this new acknowledged handoff.
|
|
121
|
+
if (x.planBinding?.schema === 'compact-slices/v2') {
|
|
122
|
+
const binding = x.planBinding;
|
|
123
|
+
const attempts = (x.routingAttempts ?? []);
|
|
124
|
+
const dispatches = x.dispatches;
|
|
125
|
+
const linked = new Set();
|
|
126
|
+
for (const dispatch of dispatches) {
|
|
127
|
+
if (!dispatch.routingAttemptId)
|
|
128
|
+
continue;
|
|
129
|
+
if (linked.has(dispatch.routingAttemptId))
|
|
130
|
+
return false;
|
|
131
|
+
linked.add(dispatch.routingAttemptId);
|
|
132
|
+
const attempt = attempts.find(candidate => candidate.id === dispatch.routingAttemptId);
|
|
133
|
+
if (!attempt || !(x.tasks.some(task => task.id === dispatch.taskId))
|
|
134
|
+
|| attempt.envelope.role !== 'implementer' || attempt.envelope.sliceId !== dispatch.taskId
|
|
135
|
+
|| attempt.envelope.planDigest !== binding.digest || attempt.envelope.executionDigest !== binding.executionDigest
|
|
136
|
+
|| !attempt.reservationRequestId || !dispatch.dispatchRequestId)
|
|
137
|
+
return false;
|
|
138
|
+
const acknowledgements = x.appliedRequests;
|
|
139
|
+
const reservationAck = acknowledgements[attempt.reservationRequestId];
|
|
140
|
+
const dispatchAck = acknowledgements[dispatch.dispatchRequestId];
|
|
141
|
+
if (reservationAck?.outcome !== 'applied' || reservationAck.requestId !== attempt.reservationRequestId
|
|
142
|
+
|| reservationAck.resultRef !== attempt.id || dispatchAck?.outcome !== 'applied'
|
|
143
|
+
|| dispatchAck.requestId !== dispatch.dispatchRequestId || dispatchAck.resultRef !== dispatch.id)
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
113
147
|
if (x.routingIncidents !== undefined && (!Array.isArray(x.routingIncidents) || x.routingIncidents.length > 4096 || !x.routingIncidents.every(isWellFormedRoutingIncident)))
|
|
114
148
|
return false;
|
|
115
149
|
if (x.implementationLineages !== undefined && (!Array.isArray(x.implementationLineages) || x.implementationLineages.length > 4096 || !x.implementationLineages.every(isWellFormedImplementationLineage)))
|
|
@@ -183,7 +217,10 @@ function isWellFormedNextAction(x) {
|
|
|
183
217
|
&& (x.state === 'pending' || x.state === 'in-progress');
|
|
184
218
|
}
|
|
185
219
|
function isWellFormedDispatch(x) {
|
|
186
|
-
return isObj(x) &&
|
|
220
|
+
return isObj(x) && Object.keys(x).every(key => ['id', 'taskId', 'at', 'routingAttemptId', 'dispatchRequestId'].includes(key))
|
|
221
|
+
&& typeof x.id === 'string' && x.id.length > 0 && typeof x.taskId === 'string' && x.taskId.length > 0
|
|
222
|
+
&& typeof x.at === 'string' && (x.routingAttemptId === undefined || (typeof x.routingAttemptId === 'string' && x.routingAttemptId.length > 0))
|
|
223
|
+
&& (x.dispatchRequestId === undefined || (typeof x.dispatchRequestId === 'string' && x.dispatchRequestId.length > 0));
|
|
187
224
|
}
|
|
188
225
|
function strings(x) {
|
|
189
226
|
return Array.isArray(x) && x.every((item) => typeof item === 'string');
|
|
@@ -235,11 +272,11 @@ function isWellFormedAppliedRequest(x) {
|
|
|
235
272
|
&& (x.resultRef === undefined || typeof x.resultRef === 'string');
|
|
236
273
|
}
|
|
237
274
|
function isWellFormedRoutingAttempt(x) {
|
|
238
|
-
return isObj(x) && Object.keys(x).every(key => ['schema', 'id', 'obligationId', 'lineageId', 'attempt', 'envelope', 'envelopeDigest', 'fingerprint', 'state', 'reservedAt', 'nativeEvidenceRequired', 'nativeEventDigest', 'nativeAgentId', 'observed', 'observedBackendModel', 'verdict', 'reasonCode', 'administrativeRepair'].includes(key))
|
|
275
|
+
return isObj(x) && Object.keys(x).every(key => ['schema', 'id', 'obligationId', 'lineageId', 'attempt', 'envelope', 'envelopeDigest', 'fingerprint', 'state', 'reservedAt', 'reservationRequestId', 'nativeEvidenceRequired', 'nativeEventDigest', 'nativeAgentId', 'observed', 'observedBackendModel', 'verdict', 'reasonCode', 'administrativeRepair'].includes(key))
|
|
239
276
|
&& x.schema === 'routing-attempt/v1'
|
|
240
277
|
&& typeof x.id === 'string' && typeof x.obligationId === 'string' && typeof x.lineageId === 'string' && Number.isSafeInteger(x.attempt) && x.attempt > 0
|
|
241
278
|
&& typeof x.envelopeDigest === 'string' && /^[a-f0-9]{64}$/.test(x.envelopeDigest) && typeof x.fingerprint === 'string' && /^[a-f0-9]{64}$/.test(x.fingerprint)
|
|
242
|
-
&& ['reserved', 'active', 'complete', 'blocked', 'unknown'].includes(String(x.state)) && (x.reservedAt === undefined || (typeof x.reservedAt === 'string' && Number.isFinite(Date.parse(x.reservedAt)))) && (x.nativeEvidenceRequired === undefined || x.nativeEvidenceRequired === true) && (x.nativeEventDigest === undefined || isDigest(x.nativeEventDigest)) && (x.nativeAgentId === undefined || typeof x.nativeAgentId === 'string') && (x.observed === undefined || isRoutingSelection(x.observed)) && (x.observedBackendModel === undefined || (typeof x.observedBackendModel === 'string' && x.observedBackendModel.length > 0 && x.observedBackendModel.length <= 128)) && (x.verdict === undefined || ['pass', 'fail', 'inconclusive'].includes(String(x.verdict))) && (x.reasonCode === undefined || typeof x.reasonCode === 'string') && (x.administrativeRepair === undefined || x.administrativeRepair === true) && isRoutingEnvelope(x.envelope);
|
|
279
|
+
&& ['reserved', 'active', 'complete', 'blocked', 'unknown'].includes(String(x.state)) && (x.reservedAt === undefined || (typeof x.reservedAt === 'string' && Number.isFinite(Date.parse(x.reservedAt)))) && (x.reservationRequestId === undefined || (typeof x.reservationRequestId === 'string' && x.reservationRequestId.length > 0 && x.reservationRequestId.length <= 128)) && (x.nativeEvidenceRequired === undefined || x.nativeEvidenceRequired === true) && (x.nativeEventDigest === undefined || isDigest(x.nativeEventDigest)) && (x.nativeAgentId === undefined || typeof x.nativeAgentId === 'string') && (x.observed === undefined || isRoutingSelection(x.observed)) && (x.observedBackendModel === undefined || (typeof x.observedBackendModel === 'string' && x.observedBackendModel.length > 0 && x.observedBackendModel.length <= 128)) && (x.verdict === undefined || ['pass', 'fail', 'inconclusive'].includes(String(x.verdict))) && (x.reasonCode === undefined || typeof x.reasonCode === 'string') && (x.administrativeRepair === undefined || x.administrativeRepair === true) && isRoutingEnvelope(x.envelope);
|
|
243
280
|
}
|
|
244
281
|
function isWellFormedRoutingIncident(x) {
|
|
245
282
|
if (!isObj(x) || Object.keys(x).length !== 12 || !Object.keys(x).every(key => ['schema', 'id', 'runStartedAt', 'target', 'selection', 'reasonCode', 'firstAt', 'lastAt', 'affectedObligations', 'fallbackCount', 'blockedCount', 'alertState'].includes(key)))
|
|
@@ -51,6 +51,7 @@ const supervisor_1 = require("../../../src/commands/watch/supervisor");
|
|
|
51
51
|
const admission_recovery_1 = require("../../../src/commands/watch/admission-recovery");
|
|
52
52
|
const watch_1 = require("../../../src/commands/watch");
|
|
53
53
|
const supervisorModule = __importStar(require("../../../src/commands/watch/supervisor"));
|
|
54
|
+
const routing_approval_1 = require("../../helpers/routing-approval");
|
|
54
55
|
const DIGEST = 'a'.repeat(64);
|
|
55
56
|
const identity = { kind: 'native', version: '1.0.0', accountScopeDigest: DIGEST };
|
|
56
57
|
const input = { provider: 'codex', routingIdentity: identity, controllerAutonomy: 'approval-free',
|
|
@@ -157,6 +158,29 @@ describe('issue #196: admission custody recovery', () => {
|
|
|
157
158
|
expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('BLOCKED');
|
|
158
159
|
expect(await (0, admission_recovery_1.recoverAdmissionCustody)(repo, 'main', input, admitted)).toBe('recovered');
|
|
159
160
|
});
|
|
161
|
+
test.each([
|
|
162
|
+
'runtime local no verificable: NATIVE_QUERY_FAILED',
|
|
163
|
+
'runtime local cambió durante generacion admitida',
|
|
164
|
+
])('runtime custody %s recovers only after strict offline revalidation', async (reason) => {
|
|
165
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'plans', 'cycle.md'), fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v2/valid.md'), 'utf8'));
|
|
166
|
+
const v2Plan = (0, validate_1.validatePlanFile)('plans/cycle.md', repo);
|
|
167
|
+
if (v2Plan.state !== 'valid' || !v2Plan.executionDigest)
|
|
168
|
+
throw new Error('invalid v2 recovery fixture');
|
|
169
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
170
|
+
state.planBinding = { ...state.planBinding, schema: v2Plan.schema, digest: v2Plan.planDigest,
|
|
171
|
+
executionDigest: v2Plan.executionDigest, executionIdentitySchema: 'awm-plan-execution/v1' };
|
|
172
|
+
state.cycle.blockedReason = reason;
|
|
173
|
+
(0, store_1.writeJournal)(repo, 'main', state);
|
|
174
|
+
const revision = (0, store_1.readJournal)(repo, 'main').state.revision;
|
|
175
|
+
await expect((0, admission_recovery_1.recoverAdmissionCustody)(repo, 'main', input, blocked('ADMISSION_SENSORS_BLOCKED', 'current', 'not-certified'))).rejects.toThrow();
|
|
176
|
+
expect((0, store_1.readJournal)(repo, 'main').state.revision).toBe(revision);
|
|
177
|
+
expect(await (0, admission_recovery_1.recoverAdmissionCustody)(repo, 'main', input, admitted)).toBe('recovered');
|
|
178
|
+
const recovered = (0, store_1.readJournal)(repo, 'main').state;
|
|
179
|
+
expect(recovered.cycle.status).toBe('IN_PROGRESS');
|
|
180
|
+
expect(recovered.planBinding?.schema).toBe('compact-slices/v2');
|
|
181
|
+
expect(recovered.admissionRecoveries?.at(-1)?.blockedReason).toBe(reason);
|
|
182
|
+
expect(recovered.generations).toEqual(state.generations);
|
|
183
|
+
});
|
|
160
184
|
test('changed binding, active request problem, pending job and identity mismatch all fail closed', async () => {
|
|
161
185
|
const check = async (mutate, pattern) => {
|
|
162
186
|
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
@@ -181,6 +205,26 @@ describe('issue #196: admission custody recovery', () => {
|
|
|
181
205
|
const sJob = (0, store_1.readJournal)(repo, 'main').state;
|
|
182
206
|
sJob.jobs = {};
|
|
183
207
|
(0, store_1.writeJournal)(repo, 'main', sJob);
|
|
208
|
+
await check(state => {
|
|
209
|
+
state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-1',
|
|
210
|
+
obligationId: 'S1-implementer', lineageId: 'S1', attempt: 1, envelope: routing_approval_1.routingEnvelope,
|
|
211
|
+
envelopeDigest: 'e'.repeat(64), fingerprint: 'f'.repeat(64), state: 'active',
|
|
212
|
+
nativeAgentId: 'native-child' }];
|
|
213
|
+
}, /nativ|routing|attempt|intento/i);
|
|
214
|
+
const sRouting = (0, store_1.readJournal)(repo, 'main').state;
|
|
215
|
+
sRouting.routingAttempts = [];
|
|
216
|
+
(0, store_1.writeJournal)(repo, 'main', sRouting);
|
|
217
|
+
await check(state => {
|
|
218
|
+
state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-1',
|
|
219
|
+
obligationId: 'S1-implementer', lineageId: 'S1', attempt: 1, envelope: routing_approval_1.routingEnvelope,
|
|
220
|
+
envelopeDigest: 'e'.repeat(64), fingerprint: 'f'.repeat(64), state: 'reserved' }];
|
|
221
|
+
state.dispatches.push({ id: 'linked', taskId: 'S1', at: new Date().toISOString(),
|
|
222
|
+
routingAttemptId: 'route-1', dispatchRequestId: 'linked-ack' });
|
|
223
|
+
}, /nativ|routing|attempt|intento|dispatch/i);
|
|
224
|
+
const sPendingChild = (0, store_1.readJournal)(repo, 'main').state;
|
|
225
|
+
sPendingChild.routingAttempts = [];
|
|
226
|
+
sPendingChild.dispatches = [];
|
|
227
|
+
(0, store_1.writeJournal)(repo, 'main', sPendingChild);
|
|
184
228
|
await check(state => { state.generations[0].processRef = (0, process_1.captureSelfRef)('recovery-test'); }, /controller/i);
|
|
185
229
|
const sController = (0, store_1.readJournal)(repo, 'main').state;
|
|
186
230
|
sController.generations[0].processRef = undefined;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -19,6 +52,7 @@ const supervisor_1 = require("../../../src/commands/watch/supervisor");
|
|
|
19
52
|
const exec_wrapper_1 = require("../../../src/commands/job/exec-wrapper");
|
|
20
53
|
const lock_1 = require("../../../src/commands/watch/lock");
|
|
21
54
|
const archive_unused_1 = require("../../../src/commands/watch/archive-unused");
|
|
55
|
+
const adapterModule = __importStar(require("../../../src/core/journal/adapter"));
|
|
22
56
|
jest.setTimeout(30000);
|
|
23
57
|
function valid(repo) {
|
|
24
58
|
const report = (0, validate_1.validatePlanFile)('docs/plan.md', repo);
|
|
@@ -43,7 +77,7 @@ describe('compact lifecycle proof and unused bootstrap archival', () => {
|
|
|
43
77
|
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), `${fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8')}\n- [ ] Reviewed task.\n`);
|
|
44
78
|
(0, init_1.initWatch)(repo, 'main', { path: 'docs/plan.md', report: valid(repo) });
|
|
45
79
|
});
|
|
46
|
-
afterEach(() => { fs_1.default.rmSync(repo, { recursive: true, force: true }); });
|
|
80
|
+
afterEach(() => { jest.restoreAllMocks(); fs_1.default.rmSync(repo, { recursive: true, force: true }); });
|
|
47
81
|
test('durable journal retains hash commitments only, never the validated plan source body', () => {
|
|
48
82
|
const contents = (directory) => fs_1.default.readdirSync(directory, { withFileTypes: true }).map(entry => entry.isDirectory() ? contents(path_1.default.join(directory, entry.name)) : fs_1.default.readFileSync(path_1.default.join(directory, entry.name), 'utf8')).join('\n');
|
|
49
83
|
expect(contents((0, paths_1.journalDir)(repo, 'main'))).not.toContain('Reviewed task.');
|
|
@@ -138,6 +172,18 @@ describe('compact lifecycle proof and unused bootstrap archival', () => {
|
|
|
138
172
|
fingerprint: (0, fingerprint_1.computeFingerprint)(repo, argv, ['source.md'], '.').fingerprint, argv, paths: ['source.md'], cwd: '.',
|
|
139
173
|
} });
|
|
140
174
|
}
|
|
175
|
+
// This fixture acts as the external controller. Give it an adopted
|
|
176
|
+
// identity before expecting the supervisor to dispatch verification
|
|
177
|
+
// jobs; launch intent alone is deliberately insufficient.
|
|
178
|
+
expect(await supervisor.tick()).toBe('continue');
|
|
179
|
+
const launched = (0, store_1.readJournal)(repo, 'main').state;
|
|
180
|
+
const gen = launched.generations.find(entry => entry.token === generation.token);
|
|
181
|
+
gen.processRef = { pid: process.pid, processGroup: process.pid, startTime: 'fixture',
|
|
182
|
+
spawnNonce: gen.spawnNonce, argvDigest: gen.launchArgvDigest, psArgsDigest: 'b'.repeat(64) };
|
|
183
|
+
(0, store_1.writeJournal)(repo, 'main', launched);
|
|
184
|
+
const adapter = adapterModule.adapterFor('codex');
|
|
185
|
+
jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter,
|
|
186
|
+
activity: () => ({ cpuTime: '0', groupSize: 1 }), safeToReplace: () => 'indeterminate' });
|
|
141
187
|
for (let i = 0; i < 100; i++) {
|
|
142
188
|
expect(await supervisor.tick()).toBe('continue');
|
|
143
189
|
if (Object.values((0, store_1.readJournal)(repo, 'main').state.jobs).some(job => job.verdict === 'pass'))
|