agentic-workflow-manager 9.11.12 → 9.12.1
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/doctor.js +23 -1
- package/dist/src/commands/hooks/claude.js +61 -8
- package/dist/src/commands/hooks/resync.js +1 -1
- package/dist/src/commands/job/gate.js +6 -1
- package/dist/src/commands/job/index.js +61 -5
- package/dist/src/commands/model-policy/index.js +253 -0
- package/dist/src/commands/plan/index.js +53 -9
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +69 -5
- package/dist/src/commands/watch/archive-unused.js +2 -0
- package/dist/src/commands/watch/index.js +24 -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 +70 -6
- package/dist/src/core/admission/index.js +20 -6
- package/dist/src/core/journal/requests.js +1 -0
- package/dist/src/core/journal/types.js +50 -6
- package/dist/src/core/model-policy/capabilities-v2.js +152 -0
- package/dist/src/core/model-policy/capabilities.js +7 -1
- package/dist/src/core/model-policy/claude-hook-events.js +120 -0
- package/dist/src/core/model-policy/event-store.js +221 -0
- package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
- package/dist/src/core/model-policy/journal.js +113 -18
- package/dist/src/core/model-policy/local-claude-scope.js +41 -0
- package/dist/src/core/model-policy/local-codex-scope.js +33 -0
- package/dist/src/core/model-policy/local-event-scope.js +13 -0
- package/dist/src/core/model-policy/machine-key.js +56 -0
- package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
- package/dist/src/core/model-policy/native-claude-config.js +107 -0
- package/dist/src/core/model-policy/native-claude-machine.js +58 -0
- package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
- package/dist/src/core/model-policy/native-claude.js +41 -0
- package/dist/src/core/model-policy/native-codex-machine.js +116 -0
- package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
- package/dist/src/core/model-policy/native-codex.js +302 -0
- package/dist/src/core/model-policy/native-routing-observe.js +106 -0
- package/dist/src/core/model-policy/native-routing-proof.js +52 -0
- package/dist/src/core/model-policy/native-runtime.js +86 -0
- package/dist/src/core/model-policy/resolve.js +96 -6
- package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
- package/dist/src/core/model-policy/setup-readiness.js +77 -0
- package/dist/src/providers/index.js +3 -1
- package/dist/tests/commands/doctor-routing.test.js +22 -0
- package/dist/tests/commands/doctor.test.js +5 -0
- package/dist/tests/commands/hooks/install.test.js +6 -0
- package/dist/tests/commands/hooks/resync.test.js +3 -0
- package/dist/tests/commands/hooks/status.test.js +12 -1
- package/dist/tests/commands/job/routing-file.test.js +32 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
- package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
- package/dist/tests/commands/model-policy/index.test.js +179 -0
- package/dist/tests/commands/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +84 -0
- package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
- package/dist/tests/commands/watch/request-recovery.test.js +253 -0
- package/dist/tests/core/admission/index.test.js +21 -0
- package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
- package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
- package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
- package/dist/tests/core/model-policy/event-store.test.js +151 -0
- package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
- package/dist/tests/core/model-policy/journal.test.js +112 -1
- package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
- package/dist/tests/core/model-policy/machine-key.test.js +38 -0
- package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
- package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
- package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
- package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
- package/dist/tests/core/model-policy/native-claude.test.js +21 -0
- package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
- package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
- package/dist/tests/core/model-policy/native-codex.test.js +133 -0
- package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
- package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
- package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
- package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
- package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
- package/dist/tests/providers/index.test.js +1 -1
- package/package.json +2 -2
- 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
|
@@ -23,6 +23,8 @@ const store_2 = require("../../core/model-policy/store");
|
|
|
23
23
|
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
24
24
|
const resolve_1 = require("../../core/model-policy/resolve");
|
|
25
25
|
const journal_1 = require("../../core/model-policy/journal");
|
|
26
|
+
const event_store_1 = require("../../core/model-policy/event-store");
|
|
27
|
+
const local_event_scope_1 = require("../../core/model-policy/local-event-scope");
|
|
26
28
|
const SUPPORTED_SCHEMA = 'compact-slices/v1, compact-slices/v2';
|
|
27
29
|
const MAX_PATH_LENGTH = 4096;
|
|
28
30
|
const MAX_DIAGNOSTICS = 20;
|
|
@@ -145,7 +147,7 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
|
|
|
145
147
|
throw new Error('program must be a Commander command');
|
|
146
148
|
assertDependencies(deps);
|
|
147
149
|
const plan = program.command('plan').description('inspect plan contracts');
|
|
148
|
-
plan.command('resolve <plan-path>').requiredOption('--provider <target>').requiredOption('--runtime-kind <kind>').requiredOption('--runtime-version <version>').requiredOption('--account-scope-digest <sha>').requiredOption('--role <role>').option('--opt-in-v1').option('--slice <id>').option('--lineage <id>').option('--cwd <path>').option('--json').action((planPath, options) => {
|
|
150
|
+
plan.command('resolve <plan-path>').requiredOption('--provider <target>').requiredOption('--runtime-kind <kind>').requiredOption('--runtime-version <version>').requiredOption('--account-scope-digest <sha>').requiredOption('--role <role>').option('--opt-in-v1').option('--slice <id>').option('--lineage <id>').option('--cwd <path>').option('--json').action(async (planPath, options) => {
|
|
149
151
|
assertText(planPath, 'plan path');
|
|
150
152
|
for (const [value, label] of [[options.provider, '--provider'], [options.runtimeKind, '--runtime-kind'], [options.runtimeVersion, '--runtime-version'], [options.accountScopeDigest, '--account-scope-digest'], [options.role, '--role']])
|
|
151
153
|
assertText(value, label);
|
|
@@ -184,11 +186,31 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
|
|
|
184
186
|
if (!roles.includes(options.role))
|
|
185
187
|
throw new Error('--role is invalid');
|
|
186
188
|
const runtime = (0, capabilities_1.validateRuntimeKey)({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest });
|
|
189
|
+
let eventFacts;
|
|
190
|
+
if (report.schema === 'compact-slices/v2' && (runtime.target === 'codex' || runtime.target === 'claude-code')) {
|
|
191
|
+
const stored = (deps.readStoredEventReceipt ?? event_store_1.readStoredEventReceipt)(runtime);
|
|
192
|
+
if (stored.state === 'invalid') {
|
|
193
|
+
process.stdout.write(`${JSON.stringify({ state: 'blocked', diagnostics: [{ code: 'ROUTING_CAPABILITY_INVALID', message: 'Native event receipt is invalid.' }] })}\n`);
|
|
194
|
+
process.exitCode = 2;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (stored.state === 'present') {
|
|
198
|
+
const local = await (runtime.target === 'codex' && deps.queryLocalCodexScope ? deps.queryLocalCodexScope(path_1.default.resolve(cwd), runtime.kind) : (0, local_event_scope_1.queryLocalEventScope)(path_1.default.resolve(cwd), runtime.target, runtime.kind));
|
|
199
|
+
if (local.state !== 'current' || JSON.stringify(local.scope.runtime) !== JSON.stringify(runtime)) {
|
|
200
|
+
const reason = local.state === 'unverified' ? local.reason : 'RUNTIME_IDENTITY_MISMATCH';
|
|
201
|
+
process.stdout.write(`${JSON.stringify({ state: 'blocked', diagnostics: [{ code: `ROUTING_${reason}`, message: `Current local ${runtime.target} scope cannot be verified (${reason}); run awm model-policy setup --provider ${runtime.target} --json.` }] })}\n`);
|
|
202
|
+
process.exitCode = 2;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
eventFacts = { eventReceipt: stored.receipt, eventScope: local.scope };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const circuitIncidents = report.schema === 'compact-slices/v2' ? journalObservation(cwd).journalState?.routingIncidents : undefined;
|
|
187
209
|
const result = report.schema === 'compact-slices/v1' && !options.optInV1 ? (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, policy: undefined, capabilities: undefined, runtime, now: new Date(), optInV1: false }) : (() => {
|
|
188
210
|
const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(cwd);
|
|
189
|
-
const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date());
|
|
211
|
+
const capabilities = eventFacts ? { state: 'absent' } : (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date());
|
|
190
212
|
if (!options.lineage) {
|
|
191
|
-
const first = (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, sliceId: options.slice, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date(), optInV1: options.optInV1 === true });
|
|
213
|
+
const first = (0, resolve_1.resolveDispatch)({ plan: report, role: options.role, sliceId: options.slice, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, ...eventFacts, circuitIncidents, runtime, now: new Date(), optInV1: options.optInV1 === true });
|
|
192
214
|
// The consumer forwards THIS envelope to `job routing-reserve`, which
|
|
193
215
|
// is what creates the lineage the --lineage escalation path below
|
|
194
216
|
// then reads. Emitting it only there made the first attempt of any
|
|
@@ -204,15 +226,15 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
|
|
|
204
226
|
const requestedProfile = options.role === 'implementer' ? slice?.implementerProfile : 'full';
|
|
205
227
|
if (requestedProfile === undefined)
|
|
206
228
|
return first;
|
|
207
|
-
return { ...first, envelope: { schema: 'routing-envelope/v1', runtime, role: options.role, planDigest: report.planDigest, executionDigest: report.executionDigest, ...(options.slice === undefined ? {} : { sliceId: options.slice }), requestedProfile, effectiveProfile: first.effectiveProfile, resolved: first.selection, policyDigest: first.policyDigest, capabilityDigest: first.capabilityDigest, outcome: first.outcome, unavailableEvidence: first.unavailableEvidence } };
|
|
229
|
+
return { ...first, envelope: { schema: 'routing-envelope/v1', runtime, role: options.role, planDigest: report.planDigest, executionDigest: report.executionDigest, ...(options.slice === undefined ? {} : { sliceId: options.slice }), requestedProfile, effectiveProfile: first.effectiveProfile, resolved: first.selection, policyDigest: first.policyDigest, capabilityDigest: first.capabilityDigest, outcome: first.outcome, unavailableEvidence: first.unavailableEvidence, ...(first.nativeAgentType ? { nativeAgentType: first.nativeAgentType } : {}) } };
|
|
208
230
|
}
|
|
209
231
|
const state = journalObservation(cwd).journalState;
|
|
210
232
|
const lineage = state.implementationLineages.find(candidate => candidate.id === options.lineage);
|
|
211
233
|
const next = (0, journal_1.resolveLineageEscalation)(state, options.lineage, lineage.initialProfile);
|
|
212
|
-
const resolved = (0, resolve_1.resolveEscalatedSelection)({ role: 'implementer', requestedProfile: next.profile, expectedEffort: next.effort, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date() });
|
|
234
|
+
const resolved = (0, resolve_1.resolveEscalatedSelection)({ role: 'implementer', requestedProfile: next.profile, expectedEffort: next.effort, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, ...eventFacts, circuitIncidents, runtime, now: new Date() });
|
|
213
235
|
if (resolved.state === 'blocked')
|
|
214
236
|
return resolved;
|
|
215
|
-
return { ...resolved, envelope: { schema: 'routing-envelope/v1', runtime, role: 'implementer', planDigest: report.planDigest, executionDigest: report.executionDigest, sliceId: lineage.sliceId, requestedProfile: lineage.initialProfile, effectiveProfile:
|
|
237
|
+
return { ...resolved, envelope: { schema: 'routing-envelope/v1', runtime, role: 'implementer', planDigest: report.planDigest, executionDigest: report.executionDigest, sliceId: lineage.sliceId, requestedProfile: lineage.initialProfile, effectiveProfile: resolved.effectiveProfile, resolved: resolved.selection, policyDigest: resolved.policyDigest, capabilityDigest: resolved.capabilityDigest, outcome: resolved.outcome, unavailableEvidence: resolved.unavailableEvidence, ...(resolved.nativeAgentType ? { nativeAgentType: resolved.nativeAgentType } : {}) }, custodyHandoff: { kind: 'routing-reserve', obligationId: lineage.obligationId, lineageId: lineage.id } };
|
|
216
238
|
})();
|
|
217
239
|
process.stdout.write(options.json ? `${JSON.stringify(result)}\n` : `Plan routing: ${result.state}\n`);
|
|
218
240
|
if (result.state === 'blocked')
|
|
@@ -290,9 +312,31 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
|
|
|
290
312
|
process.exitCode = 2;
|
|
291
313
|
return;
|
|
292
314
|
}
|
|
293
|
-
const readRouting = () => {
|
|
294
|
-
|
|
295
|
-
|
|
315
|
+
const readRouting = async () => {
|
|
316
|
+
if (!options.runtimeKind || !options.runtimeVersion || !options.accountScopeDigest)
|
|
317
|
+
return undefined;
|
|
318
|
+
const runtime = (0, capabilities_1.validateRuntimeKey)({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest });
|
|
319
|
+
const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(options.cwd);
|
|
320
|
+
const at = deps.routingNow?.() ?? new Date();
|
|
321
|
+
if (!(at instanceof Date) || !Number.isFinite(at.getTime()))
|
|
322
|
+
throw new Error('routingNow must return a finite Date');
|
|
323
|
+
const base = { runtime, policy: policy.state === 'approved' ? policy.policy : undefined, now: at };
|
|
324
|
+
if (runtime.target === 'codex' || runtime.target === 'claude-code') {
|
|
325
|
+
const stored = (deps.readStoredEventReceipt ?? event_store_1.readStoredEventReceipt)(runtime);
|
|
326
|
+
if (stored.state === 'invalid')
|
|
327
|
+
return { ...base, eventIssue: { code: 'ROUTING_CAPABILITY_INVALID', message: 'Sealed native event receipt is invalid.' } };
|
|
328
|
+
if (stored.state === 'present') {
|
|
329
|
+
const local = await (runtime.target === 'codex' && deps.queryLocalCodexScope ? deps.queryLocalCodexScope(path_1.default.resolve(options.cwd), runtime.kind) : (0, local_event_scope_1.queryLocalEventScope)(path_1.default.resolve(options.cwd), runtime.target, runtime.kind));
|
|
330
|
+
if (local.state !== 'current' || JSON.stringify(local.scope.runtime) !== JSON.stringify(runtime)) {
|
|
331
|
+
const reason = local.state === 'unverified' ? local.reason : 'RUNTIME_IDENTITY_MISMATCH';
|
|
332
|
+
return { ...base, eventIssue: { code: `ROUTING_${reason}`, message: `Local ${runtime.target} scope cannot be verified (${reason}); run awm model-policy setup --provider ${runtime.target} --json.` } };
|
|
333
|
+
}
|
|
334
|
+
return { ...base, eventReceipt: stored.receipt, eventScope: local.scope };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, at);
|
|
338
|
+
return { ...base, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined };
|
|
339
|
+
};
|
|
296
340
|
if (options.controllerAutonomy !== undefined && !(0, adapter_1.isControllerAutonomy)(options.controllerAutonomy))
|
|
297
341
|
throw new Error(`--controller-autonomy is invalid: ${options.controllerAutonomy} (valid: ${adapter_1.CONTROLLER_AUTONOMIES.join(', ')})`);
|
|
298
342
|
const report = await (0, registry_contracts_1.admitRegistryPlan)({ plan: planReport, provider: options.provider, cwd: options.cwd, enabledAgents, executionMode, planPath: normalizedPlanPath, ...journal, requireCurrent: options.requireCurrent === true, verifySensors: options.verifySensors === true, ...(options.controllerAutonomy === undefined ? {} : { controllerAutonomy: options.controllerAutonomy }) }, { admitPlan: admission, listRegistries: registryInventory, checkCurrentness: currentnessCheck, runSensors: sensorRun, readRouting });
|
|
@@ -17,7 +17,24 @@ const registries_1 = require("../../core/registries");
|
|
|
17
17
|
const inspect_1 = require("../../core/context-kernel/inspect");
|
|
18
18
|
const registries_2 = require("../../core/registries");
|
|
19
19
|
const check_1 = require("../../core/currentness/check");
|
|
20
|
+
const store_1 = require("../../core/model-policy/store");
|
|
21
|
+
const setup_readiness_1 = require("../../core/model-policy/setup-readiness");
|
|
20
22
|
const MANIFEST = path_1.default.join('.awm', 'sensors.json');
|
|
23
|
+
function checkRoutingSetup(cwd) {
|
|
24
|
+
const guidance = (0, setup_readiness_1.routingSetupGuidance)((0, store_1.readEffectivePolicy)(cwd));
|
|
25
|
+
if (guidance.length === 0)
|
|
26
|
+
return [];
|
|
27
|
+
const invalid = guidance.find(item => item.state === 'policy-invalid');
|
|
28
|
+
if (invalid?.state === 'policy-invalid')
|
|
29
|
+
return [{ id: 'routing-setup', ok: false, advisory: true, detail: 'approved routing policy is invalid', remedy: 'repair the model policy before using routed subagents' }];
|
|
30
|
+
const targets = guidance.filter((item) => item.state === 'needs-evidence');
|
|
31
|
+
if (targets.length === 0)
|
|
32
|
+
return [{ id: 'routing-setup', ok: true, advisory: true,
|
|
33
|
+
detail: 'sealed routing selections are covered locally; doctor/preflight do not verify current runtime/account/config scope (checked at routed dispatch)' }];
|
|
34
|
+
return [{ id: 'routing-setup', ok: false, advisory: true,
|
|
35
|
+
detail: `routing machine evidence needs a native check for ${targets.map(item => `${item.target}/${item.runtimeKind}`).join(', ')}`,
|
|
36
|
+
remedy: targets[0].command }];
|
|
37
|
+
}
|
|
21
38
|
/**
|
|
22
39
|
* The agent needs project context delivered every session. A repo with neither file
|
|
23
40
|
* hands every agent — and every teammate's agent — a blank slate.
|
|
@@ -472,6 +489,7 @@ async function preflight(cwd = process.cwd(), opts = {}) {
|
|
|
472
489
|
...(opts.verifySensors === true ? [await checkSensorExecution(cwd)] : []),
|
|
473
490
|
// Strict mode is the only path that contacts authoritative npm/Git sources.
|
|
474
491
|
...(opts.requireCurrent === true ? [checkCompatibility()] : []),
|
|
492
|
+
...checkRoutingSetup(cwd),
|
|
475
493
|
// Runs unconditionally — orthogonal to sensor configuration entirely, this is
|
|
476
494
|
// about PR/MR tooling, not sensors.
|
|
477
495
|
checkHost(cwd),
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.applyCyclePlanAppend = applyCyclePlanAppend;
|
|
7
7
|
exports.registerTrackIntegrationItems = registerTrackIntegrationItems;
|
|
8
|
+
exports.applyRequestToState = applyRequestToState;
|
|
8
9
|
exports.consumePendingRequests = consumePendingRequests;
|
|
9
10
|
// Consumo transaccional de requests (bloqueador 4): mutar estado ->
|
|
10
11
|
// writeJournal -> RECIEN AHI borrar archivos. El replay es seguro por
|
|
@@ -22,7 +23,28 @@ const types_1 = require("../../core/journal/types");
|
|
|
22
23
|
const journal_1 = require("../../core/model-policy/journal");
|
|
23
24
|
const store_2 = require("../../core/model-policy/store");
|
|
24
25
|
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
26
|
+
const event_store_1 = require("../../core/model-policy/event-store");
|
|
27
|
+
const capabilities_v2_1 = require("../../core/model-policy/capabilities-v2");
|
|
28
|
+
const machine_key_1 = require("../../core/model-policy/machine-key");
|
|
29
|
+
const paths_2 = require("../../core/paths");
|
|
30
|
+
const native_routing_proof_1 = require("../../core/model-policy/native-routing-proof");
|
|
31
|
+
const selection_policy_digest_1 = require("../../core/model-policy/selection-policy-digest");
|
|
25
32
|
function now() { return new Date().toISOString(); }
|
|
33
|
+
function currentNativeAttempt(repoRoot, envelope, eventScope, at) {
|
|
34
|
+
if (!eventScope || JSON.stringify(eventScope.runtime) !== JSON.stringify(envelope.runtime))
|
|
35
|
+
return false;
|
|
36
|
+
try {
|
|
37
|
+
const stored = (0, event_store_1.readStoredEventReceipt)((0, capabilities_1.validateRuntimeKey)(envelope.runtime));
|
|
38
|
+
const approved = (0, store_2.readEffectivePolicy)(repoRoot);
|
|
39
|
+
if (stored.state !== 'present' || approved.state !== 'approved' || approved.policy.contentDigest !== envelope.policyDigest)
|
|
40
|
+
return false;
|
|
41
|
+
const mapping = approved.policy.content.mappings.find(row => row.target === envelope.runtime.target && row.runtimeKind === envelope.runtime.kind);
|
|
42
|
+
return !!mapping && (0, capabilities_v2_1.evaluateEventReceipt)(stored.receipt, eventScope, envelope.resolved, (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, envelope.resolved), at).state === 'current';
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
26
48
|
const VERIFICATION_KINDS = ['test', 'lint', 'sensors', 'review', 'qa', 'interlock', 'track-integration'];
|
|
27
49
|
function verificationItems(value, field) {
|
|
28
50
|
if (!Array.isArray(value) || !value.every((item) => typeof item === 'object' && item !== null
|
|
@@ -107,7 +129,7 @@ function registerTrackIntegrationItems(state) {
|
|
|
107
129
|
function trackWorktreePath(repoRoot, trackId) {
|
|
108
130
|
return path_1.default.join(path_1.default.dirname(repoRoot), `${path_1.default.basename(repoRoot)}.track-${trackId}`);
|
|
109
131
|
}
|
|
110
|
-
function applyRequestToState(s, env, digest, repoRoot) {
|
|
132
|
+
function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
111
133
|
const base = { requestId: env.requestId, idempotencyKey: env.idempotencyKey, payloadDigest: digest };
|
|
112
134
|
if (env.kind === 'controller-heartbeat') {
|
|
113
135
|
s.controllerHeartbeatAt = now();
|
|
@@ -122,6 +144,14 @@ function applyRequestToState(s, env, digest, repoRoot) {
|
|
|
122
144
|
const reserved = (0, journal_1.reserveRoutingAttempt)(s, { obligationId: p.obligationId, lineageId: p.lineageId, envelope, fingerprint: p.fingerprint, approval: () => {
|
|
123
145
|
const checkedAt = new Date();
|
|
124
146
|
const policy = (0, store_2.readEffectivePolicy)(repoRoot);
|
|
147
|
+
const event = (0, event_store_1.readStoredEventReceipt)((0, capabilities_1.validateRuntimeKey)(envelope.runtime));
|
|
148
|
+
if (event.state === 'invalid')
|
|
149
|
+
throw new Error('routing native event receipt is invalid');
|
|
150
|
+
if (event.state === 'present') {
|
|
151
|
+
if (!eventScope)
|
|
152
|
+
throw new Error('routing native event scope is unverified');
|
|
153
|
+
return { checkedAt, policy: policy.state === 'approved' ? policy.policy : undefined, eventReceipt: event.receipt, eventScope };
|
|
154
|
+
}
|
|
125
155
|
const capabilities = (0, capabilities_1.readCapabilities)((0, capabilities_1.validateRuntimeKey)(envelope.runtime), checkedAt);
|
|
126
156
|
return { checkedAt, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined };
|
|
127
157
|
} }, p.at ?? now());
|
|
@@ -133,7 +163,36 @@ function applyRequestToState(s, env, digest, repoRoot) {
|
|
|
133
163
|
const p = env.payload;
|
|
134
164
|
if (typeof p.attemptId !== 'string' || typeof p.nativeAgentId !== 'string' || (p.observed !== undefined && !(0, types_1.isRoutingSelection)(p.observed)) || (p.unavailableReason !== undefined && typeof p.unavailableReason !== 'string') || (p.at !== undefined && typeof p.at !== 'string'))
|
|
135
165
|
throw new Error('routing-observe requiere payload estricto');
|
|
136
|
-
const
|
|
166
|
+
const attempt = s.routingAttempts?.find(item => item.id === p.attemptId);
|
|
167
|
+
if (!attempt)
|
|
168
|
+
throw new Error('routing-observe attempt is unknown');
|
|
169
|
+
let key = null;
|
|
170
|
+
if (attempt.nativeEvidenceRequired) {
|
|
171
|
+
try {
|
|
172
|
+
key = (0, machine_key_1.readMachineKey)((0, paths_2.awmHome)());
|
|
173
|
+
}
|
|
174
|
+
catch { /* missing/unsafe machine key is recorded as PROVENANCE_MISSING */ }
|
|
175
|
+
}
|
|
176
|
+
const proof = p.nativeProof;
|
|
177
|
+
const nativeProof = proof;
|
|
178
|
+
const checkedAt = new Date();
|
|
179
|
+
const validProof = attempt.nativeEvidenceRequired && key && attempt.reservedAt
|
|
180
|
+
? (0, native_routing_proof_1.verifyNativeRoutingProof)(proof, { attemptId: p.attemptId, nativeAgentId: p.nativeAgentId,
|
|
181
|
+
reservedAt: attempt.reservedAt, selection: attempt.envelope.resolved }, key, checkedAt)
|
|
182
|
+
&& checkedAt.getTime() - Date.parse(nativeProof.observedAt) <= 15 * 60_000
|
|
183
|
+
&& currentNativeAttempt(repoRoot, attempt.envelope, eventScope, checkedAt)
|
|
184
|
+
&& (attempt.envelope.runtime.target === 'codex' ? nativeProof?.source === 'codex-turn-context' : nativeProof?.source === 'claude-assistant-transcript')
|
|
185
|
+
&& p.observed !== undefined && JSON.stringify(p.observed) === JSON.stringify(nativeProof?.selection)
|
|
186
|
+
&& !s.routingAttempts?.some(item => item.id !== attempt.id && item.nativeEvidenceRequired
|
|
187
|
+
&& (item.nativeAgentId === p.nativeAgentId || item.nativeEventDigest === nativeProof?.eventDigest))
|
|
188
|
+
: false;
|
|
189
|
+
const observed = (0, journal_1.observeRoutingAttempt)(s, { attemptId: p.attemptId, nativeAgentId: p.nativeAgentId,
|
|
190
|
+
...(validProof ? { observed: nativeProof.selection, nativeEventDigest: nativeProof.eventDigest,
|
|
191
|
+
...(nativeProof.actualModel === undefined ? {} : { observedBackendModel: nativeProof.actualModel }) }
|
|
192
|
+
: attempt.nativeEvidenceRequired ? {} : p.observed === undefined ? {} : { observed: p.observed }),
|
|
193
|
+
...(attempt.nativeEvidenceRequired
|
|
194
|
+
? validProof ? {} : { unavailableReason: p.unavailableReason === 'PROVIDER_REJECTED' ? 'PROVIDER_REJECTED' : 'PROVENANCE_MISSING' }
|
|
195
|
+
: p.unavailableReason === undefined ? {} : { unavailableReason: p.unavailableReason }) }, p.at ?? now());
|
|
137
196
|
Object.assign(s, observed);
|
|
138
197
|
(0, requests_1.applyOutcome)(s, { ...base, outcome: 'applied', resultRef: p.attemptId });
|
|
139
198
|
return;
|
|
@@ -444,6 +503,8 @@ function applyRequestToState(s, env, digest, repoRoot) {
|
|
|
444
503
|
}
|
|
445
504
|
if (s.cycle.status !== 'BLOCKED')
|
|
446
505
|
throw new Error('custody-decision solo aplica a un ciclo BLOCKED');
|
|
506
|
+
if ((0, types_1.activeRequestProblems)(s).length > 0)
|
|
507
|
+
throw new Error('custody-decision no resuelve requests rechazadas pendientes');
|
|
447
508
|
s.custodyDecisions ??= [];
|
|
448
509
|
s.custodyDecisions.push({ at: now(), decision: 'resume', reason: p.reason, generationToken: env.generationToken });
|
|
449
510
|
for (const generation of s.generations) {
|
|
@@ -541,7 +602,7 @@ function applyRequestToState(s, env, digest, repoRoot) {
|
|
|
541
602
|
/** Consume TODAS las requests pendientes en orden. ORDEN CRITICO (R1.3,
|
|
542
603
|
* bloqueador 4): (1) mutar estado, (2) writeJournal, (3) borrar archivos,
|
|
543
604
|
* (4) fsync del directorio. Solo el supervisor llama esto (single-writer). */
|
|
544
|
-
function consumePendingRequests(repoRoot, branch, activeToken) {
|
|
605
|
+
function consumePendingRequests(repoRoot, branch, activeToken, eventScope) {
|
|
545
606
|
const r = (0, store_1.readJournal)(repoRoot, branch);
|
|
546
607
|
if (r.corrupt || r.state === null)
|
|
547
608
|
throw new Error('journal corrupto: el supervisor no opera sobre corrupcion (R1.6)');
|
|
@@ -599,7 +660,7 @@ function consumePendingRequests(repoRoot, branch, activeToken) {
|
|
|
599
660
|
// nunca deja un estado parcialmente contaminado que luego no se
|
|
600
661
|
// pueda serializar o reintentar.
|
|
601
662
|
const candidate = structuredClone(s);
|
|
602
|
-
applyRequestToState(candidate, env, digest, repoRoot);
|
|
663
|
+
applyRequestToState(candidate, env, digest, repoRoot, eventScope);
|
|
603
664
|
s = candidate;
|
|
604
665
|
applied++;
|
|
605
666
|
stateTouched = true;
|
|
@@ -608,7 +669,10 @@ function consumePendingRequests(repoRoot, branch, activeToken) {
|
|
|
608
669
|
rejectedInvalid++;
|
|
609
670
|
deferredRenames.push({ from: p.file, to: `${p.file}.rejected` });
|
|
610
671
|
if (!s.requestProblems.some((problem) => problem.file === p.file && problem.kind === 'rejected')) {
|
|
611
|
-
s.requestProblems.push({ file: p.file, kind: 'rejected', detail: (0, redact_1.redactText)(e.message), at: now()
|
|
672
|
+
s.requestProblems.push({ file: p.file, kind: 'rejected', detail: (0, redact_1.redactText)(e.message), at: now(),
|
|
673
|
+
requestId: env.requestId, generationToken: env.generationToken,
|
|
674
|
+
...(env.kind === 'register-entity' && env.payload.entity === 'task' && typeof env.payload.taskId === 'string'
|
|
675
|
+
? { taskId: env.payload.taskId } : {}) });
|
|
612
676
|
}
|
|
613
677
|
(0, store_1.appendEvent)(repoRoot, branch, { kind: 'request-rejected-invalid', requestId: env.requestId, detail: e.message });
|
|
614
678
|
dirChanged = true;
|
|
@@ -50,8 +50,10 @@ function watchJournalStatus(repoRoot, branch) {
|
|
|
50
50
|
}
|
|
51
51
|
catch { /* recognized present journal, but never eligible by absence alone */ }
|
|
52
52
|
const binding = current.state.planBinding;
|
|
53
|
+
const latest = current.state.generations.reduce((prev, generation) => prev === undefined || generation.n > prev.n ? generation : prev, undefined);
|
|
53
54
|
return {
|
|
54
55
|
state: 'present', cycleState: current.state.cycle.status, bootstrapUnused,
|
|
56
|
+
...(latest ? { latestGeneration: { n: latest.n, token: latest.token, state: latest.state } } : {}),
|
|
55
57
|
...(binding ? { binding: { path: binding.path, digest: binding.digest, ...(binding.executionDigest ? { executionDigest: binding.executionDigest, executionIdentitySchema: binding.executionIdentitySchema } : {}), schema: binding.schema, executionMode: binding.executionMode, boundAt: binding.boundAt } } : {}),
|
|
56
58
|
};
|
|
57
59
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.registerWatchCommand = registerWatchCommand;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const init_1 = require("./init");
|
|
9
|
+
const request_recovery_1 = require("./request-recovery");
|
|
9
10
|
const supervisor_1 = require("./supervisor");
|
|
10
11
|
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
11
12
|
const adapter_1 = require("../../core/journal/adapter");
|
|
@@ -144,6 +145,29 @@ function registerWatchCommand(program) {
|
|
|
144
145
|
await (0, supervisor_1.runSupervisorLoop)(repo, branch, cfg);
|
|
145
146
|
process.stdout.write('gate verde: ciclo COMPLETE — drenado, lock liberado, apagando\n');
|
|
146
147
|
});
|
|
148
|
+
watch.command('recover-request')
|
|
149
|
+
.description('reconcilia una task rechazada con una request correctiva aplicada bajo lock exclusivo')
|
|
150
|
+
.requiredOption('--rejected <requestId>', 'request rechazada archivada')
|
|
151
|
+
.requiredOption('--replacement <requestId>', 'request correctiva pendiente')
|
|
152
|
+
.requiredOption('--generation <token>', 'generacion vigente')
|
|
153
|
+
.requiredOption('--reason <text>', 'motivo auditado de la recuperacion')
|
|
154
|
+
.option('--resume', 'reanuda explicitamente custodia si el unico bloqueo era la request')
|
|
155
|
+
.action((opts) => {
|
|
156
|
+
const repo = process.cwd();
|
|
157
|
+
const branch = currentBranch(repo);
|
|
158
|
+
try {
|
|
159
|
+
(0, context_1.resolveCommandContext)(repo, branch);
|
|
160
|
+
(0, request_recovery_1.recoverRejectedTask)(repo, branch, {
|
|
161
|
+
rejectedRequestId: opts.rejected, replacementRequestId: opts.replacement,
|
|
162
|
+
generationToken: opts.generation, reason: opts.reason, resume: opts.resume === true,
|
|
163
|
+
});
|
|
164
|
+
process.stdout.write(JSON.stringify({ recovered: true, rejectedRequestId: opts.rejected, replacementRequestId: opts.replacement, resumed: opts.resume === true }) + '\n');
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
process.stderr.write(`${error.message}\n`);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
147
171
|
watch
|
|
148
172
|
.command('rebind')
|
|
149
173
|
.description('reconcilia intencionalmente el binding desatendido tras un cambio válido del ciclo del plan')
|
|
@@ -13,6 +13,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const store_1 = require("../../core/journal/store");
|
|
15
15
|
const paths_1 = require("../../core/journal/paths");
|
|
16
|
+
const types_1 = require("../../core/journal/types");
|
|
16
17
|
const lock_1 = require("./lock");
|
|
17
18
|
const validate_1 = require("../../core/plan/validate");
|
|
18
19
|
const identity_1 = require("../../core/plan/identity");
|
|
@@ -104,7 +105,7 @@ function rebindBlocker(state) {
|
|
|
104
105
|
const nonterminal = Object.values(state.jobs).filter(job => !TERMINAL_JOB_STATES.has(job.executionState));
|
|
105
106
|
if (nonterminal.length > 0)
|
|
106
107
|
return `hay jobs no terminales: ${nonterminal.map(job => job.id).join(', ')}`;
|
|
107
|
-
if (state.
|
|
108
|
+
if ((0, types_1.activeRequestProblems)(state).length > 0)
|
|
108
109
|
return 'hay conflictos durables de requests';
|
|
109
110
|
if (state.verdicts.some(verdict => verdict.result !== 'pass'))
|
|
110
111
|
return 'hay evidencia adversa durable';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.recoverRejectedTask = recoverRejectedTask;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const json_1 = require("../../core/plan/json");
|
|
10
|
+
const atomic_file_1 = require("../../core/atomic-file");
|
|
11
|
+
const types_1 = require("../../core/journal/types");
|
|
12
|
+
const requests_1 = require("../../core/journal/requests");
|
|
13
|
+
const paths_1 = require("../../core/journal/paths");
|
|
14
|
+
const store_1 = require("../../core/journal/store");
|
|
15
|
+
const process_1 = require("../../core/journal/process");
|
|
16
|
+
const apply_1 = require("./apply");
|
|
17
|
+
const lock_1 = require("./lock");
|
|
18
|
+
const REQUEST_ID = /^req-[0-9]+-[0-9]{10}-[a-f0-9]{8}$/;
|
|
19
|
+
const MAX_REQUEST_BYTES = 256 * 1024;
|
|
20
|
+
function validatedId(value, label) {
|
|
21
|
+
if (typeof value !== 'string' || !REQUEST_ID.test(value))
|
|
22
|
+
throw new Error(`${label} requiere un requestId valido`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
/** Reads only one canonical, bounded request file. The inode check closes a
|
|
26
|
+
* swap between lstat and open, including platforms without O_NOFOLLOW. */
|
|
27
|
+
function requestEnvelope(file, requestId) {
|
|
28
|
+
const before = fs_1.default.lstatSync(file);
|
|
29
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size < 1 || before.size > MAX_REQUEST_BYTES) {
|
|
30
|
+
throw new Error('request de recovery no es un archivo regular acotado');
|
|
31
|
+
}
|
|
32
|
+
const descriptor = fs_1.default.openSync(file, fs_1.default.constants.O_RDONLY | (fs_1.default.constants.O_NOFOLLOW ?? 0));
|
|
33
|
+
let body;
|
|
34
|
+
try {
|
|
35
|
+
const opened = fs_1.default.fstatSync(descriptor);
|
|
36
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
|
|
37
|
+
throw new Error('request de recovery cambio de identidad');
|
|
38
|
+
}
|
|
39
|
+
body = fs_1.default.readFileSync(descriptor, { encoding: 'utf8' });
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
fs_1.default.closeSync(descriptor);
|
|
43
|
+
}
|
|
44
|
+
const parsed = (0, json_1.parseJsonNoDuplicate)(body);
|
|
45
|
+
if (!(0, requests_1.isWellFormedEnvelope)(parsed) || parsed.requestId !== requestId)
|
|
46
|
+
throw new Error('request de recovery tiene envelope invalido');
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
function taskId(envelope) {
|
|
50
|
+
if (envelope.kind !== 'register-entity' || envelope.payload.entity !== 'task'
|
|
51
|
+
|| typeof envelope.payload.taskId !== 'string' || envelope.payload.taskId.length === 0) {
|
|
52
|
+
throw new Error('recovery solo admite register-entity task con taskId');
|
|
53
|
+
}
|
|
54
|
+
return envelope.payload.taskId;
|
|
55
|
+
}
|
|
56
|
+
function mayResume(state) {
|
|
57
|
+
if (state.cycle.status !== 'BLOCKED' || state.cycle.blockedReason !== 'recovery no autorizado: hay conflictos durables de requests') {
|
|
58
|
+
throw new Error('resume requiere custodia BLOCKED por requests');
|
|
59
|
+
}
|
|
60
|
+
if ((0, types_1.activeRequestProblems)(state).length > 0)
|
|
61
|
+
throw new Error('resume requiere resolver todas las requests activas');
|
|
62
|
+
}
|
|
63
|
+
/** Offline recovery lane: no supervisor or controller may own this journal.
|
|
64
|
+
* The correction and its audit link are published in one state transaction. */
|
|
65
|
+
function recoverRejectedTask(repoRoot, branch, input) {
|
|
66
|
+
if (typeof repoRoot !== 'string' || !path_1.default.isAbsolute(repoRoot) || typeof branch !== 'string' || branch.length === 0
|
|
67
|
+
|| !input || typeof input !== 'object' || typeof input.generationToken !== 'string' || input.generationToken.length === 0
|
|
68
|
+
|| typeof input.reason !== 'string' || input.reason.trim().length === 0 || input.reason.length > 1024 || typeof input.resume !== 'boolean') {
|
|
69
|
+
throw new Error('parametros de recovery invalidos');
|
|
70
|
+
}
|
|
71
|
+
const rejectedId = validatedId(input.rejectedRequestId, '--rejected');
|
|
72
|
+
const replacementId = validatedId(input.replacementRequestId, '--replacement');
|
|
73
|
+
if (rejectedId === replacementId)
|
|
74
|
+
throw new Error('la request correctiva debe ser distinta');
|
|
75
|
+
const lock = (0, lock_1.acquireLock)(repoRoot);
|
|
76
|
+
try {
|
|
77
|
+
const read = (0, store_1.readJournal)(repoRoot, branch);
|
|
78
|
+
if (read.corrupt || !read.state || read.state.schema !== 2 || !read.state.planBinding)
|
|
79
|
+
throw new Error('recovery requiere journal schema-2 vigente');
|
|
80
|
+
const state = read.state;
|
|
81
|
+
const latest = [...state.generations].sort((a, b) => b.n - a.n)[0];
|
|
82
|
+
if (!latest || latest.token !== input.generationToken)
|
|
83
|
+
throw new Error('generation de recovery no es la vigente');
|
|
84
|
+
for (const generation of state.generations) {
|
|
85
|
+
for (const ref of [generation.processRef, generation.wrapperRef]) {
|
|
86
|
+
if (ref && (0, process_1.refIsAlive)(ref))
|
|
87
|
+
throw new Error('controller vivo: recovery requiere custodia exclusiva');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const dir = (0, paths_1.requestsDir)(repoRoot, branch);
|
|
91
|
+
const originalFile = path_1.default.join(dir, `${rejectedId}.json`);
|
|
92
|
+
const problem = state.requestProblems.find(item => item.kind === 'rejected' && item.file === originalFile);
|
|
93
|
+
if (!problem)
|
|
94
|
+
throw new Error('rechazo durable no encontrado');
|
|
95
|
+
if (problem.resolution) {
|
|
96
|
+
if (problem.resolution.replacementRequestId !== replacementId || problem.resolution.generationToken !== input.generationToken || problem.resolution.reason !== input.reason
|
|
97
|
+
|| (0, types_1.activeRequestProblems)(state).includes(problem))
|
|
98
|
+
throw new Error('recovery previo incompatible');
|
|
99
|
+
if (input.resume && state.cycle.status === 'BLOCKED') {
|
|
100
|
+
const candidate = structuredClone(state);
|
|
101
|
+
mayResume(candidate);
|
|
102
|
+
candidate.custodyDecisions ??= [];
|
|
103
|
+
candidate.custodyDecisions.push({ at: new Date().toISOString(), decision: 'resume', reason: input.reason, generationToken: input.generationToken });
|
|
104
|
+
candidate.cycle.status = 'IN_PROGRESS';
|
|
105
|
+
candidate.cycle.blockedReason = undefined;
|
|
106
|
+
for (const generation of candidate.generations)
|
|
107
|
+
if (generation.state === 'active' || generation.state === 'controller-suspected-stall')
|
|
108
|
+
generation.state = 'superseded';
|
|
109
|
+
(0, store_1.writeJournal)(repoRoot, branch, candidate);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const original = requestEnvelope(`${originalFile}.rejected`, rejectedId);
|
|
114
|
+
const replacementFile = path_1.default.join(dir, `${replacementId}.json`);
|
|
115
|
+
const replacement = requestEnvelope(replacementFile, replacementId);
|
|
116
|
+
if (original.generationToken !== input.generationToken || replacement.generationToken !== input.generationToken || taskId(original) !== taskId(replacement)) {
|
|
117
|
+
throw new Error('request correctiva no enlaza el mismo taskId o generation');
|
|
118
|
+
}
|
|
119
|
+
if (state.tasks.some(task => task.id === taskId(original)))
|
|
120
|
+
throw new Error('task ya existente: recovery requiere un registro rechazado sin aplicar');
|
|
121
|
+
if (state.appliedRequests[replacementId] !== undefined || Object.values(state.appliedRequests).some(item => item.idempotencyKey === replacement.idempotencyKey && item.payloadDigest !== (0, requests_1.digestOf)(replacement.payload))) {
|
|
122
|
+
throw new Error('request correctiva ya tiene outcome o idempotencyKey conflictiva');
|
|
123
|
+
}
|
|
124
|
+
const candidate = structuredClone(state);
|
|
125
|
+
(0, apply_1.applyRequestToState)(candidate, replacement, (0, requests_1.digestOf)(replacement.payload), repoRoot);
|
|
126
|
+
if (candidate.appliedRequests[replacementId]?.outcome !== 'applied' || !candidate.tasks.some(task => task.id === taskId(replacement))) {
|
|
127
|
+
throw new Error('request correctiva no fue aplicada');
|
|
128
|
+
}
|
|
129
|
+
const target = candidate.requestProblems.find(item => item.kind === 'rejected' && item.file === originalFile);
|
|
130
|
+
target.requestId = rejectedId;
|
|
131
|
+
target.taskId = taskId(original);
|
|
132
|
+
target.generationToken = original.generationToken;
|
|
133
|
+
target.resolution = { replacementRequestId: replacementId, replacementPayloadDigest: (0, requests_1.digestOf)(replacement.payload), reason: input.reason,
|
|
134
|
+
generationToken: input.generationToken, at: new Date().toISOString() };
|
|
135
|
+
if (input.resume) {
|
|
136
|
+
mayResume(candidate);
|
|
137
|
+
candidate.custodyDecisions ??= [];
|
|
138
|
+
candidate.custodyDecisions.push({ at: new Date().toISOString(), decision: 'resume', reason: input.reason, generationToken: input.generationToken });
|
|
139
|
+
candidate.cycle.status = 'IN_PROGRESS';
|
|
140
|
+
candidate.cycle.blockedReason = undefined;
|
|
141
|
+
for (const generation of candidate.generations)
|
|
142
|
+
if (generation.state === 'active' || generation.state === 'controller-suspected-stall')
|
|
143
|
+
generation.state = 'superseded';
|
|
144
|
+
}
|
|
145
|
+
(0, store_1.writeJournal)(repoRoot, branch, candidate);
|
|
146
|
+
fs_1.default.rmSync(replacementFile);
|
|
147
|
+
(0, atomic_file_1.fsyncDirSync)(dir);
|
|
148
|
+
(0, store_1.appendEvent)(repoRoot, branch, { kind: 'request-recovered', rejectedRequestId: rejectedId, replacementRequestId: replacementId, generationToken: input.generationToken, resumed: input.resume });
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
(0, lock_1.releaseLock)(repoRoot, lock);
|
|
152
|
+
}
|
|
153
|
+
}
|