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.
Files changed (83) hide show
  1. package/dist/src/commands/doctor.js +23 -1
  2. package/dist/src/commands/hooks/claude.js +61 -8
  3. package/dist/src/commands/hooks/resync.js +1 -1
  4. package/dist/src/commands/job/gate.js +6 -1
  5. package/dist/src/commands/job/index.js +61 -5
  6. package/dist/src/commands/model-policy/index.js +253 -0
  7. package/dist/src/commands/plan/index.js +53 -9
  8. package/dist/src/commands/preflight/checks.js +18 -0
  9. package/dist/src/commands/watch/apply.js +69 -5
  10. package/dist/src/commands/watch/archive-unused.js +2 -0
  11. package/dist/src/commands/watch/index.js +24 -0
  12. package/dist/src/commands/watch/init.js +2 -1
  13. package/dist/src/commands/watch/request-recovery.js +153 -0
  14. package/dist/src/commands/watch/supervisor.js +70 -6
  15. package/dist/src/core/admission/index.js +20 -6
  16. package/dist/src/core/journal/requests.js +1 -0
  17. package/dist/src/core/journal/types.js +50 -6
  18. package/dist/src/core/model-policy/capabilities-v2.js +152 -0
  19. package/dist/src/core/model-policy/capabilities.js +7 -1
  20. package/dist/src/core/model-policy/claude-hook-events.js +120 -0
  21. package/dist/src/core/model-policy/event-store.js +221 -0
  22. package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
  23. package/dist/src/core/model-policy/journal.js +113 -18
  24. package/dist/src/core/model-policy/local-claude-scope.js +41 -0
  25. package/dist/src/core/model-policy/local-codex-scope.js +33 -0
  26. package/dist/src/core/model-policy/local-event-scope.js +13 -0
  27. package/dist/src/core/model-policy/machine-key.js +56 -0
  28. package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
  29. package/dist/src/core/model-policy/native-claude-config.js +107 -0
  30. package/dist/src/core/model-policy/native-claude-machine.js +58 -0
  31. package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
  32. package/dist/src/core/model-policy/native-claude.js +41 -0
  33. package/dist/src/core/model-policy/native-codex-machine.js +116 -0
  34. package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
  35. package/dist/src/core/model-policy/native-codex.js +302 -0
  36. package/dist/src/core/model-policy/native-routing-observe.js +106 -0
  37. package/dist/src/core/model-policy/native-routing-proof.js +52 -0
  38. package/dist/src/core/model-policy/native-runtime.js +86 -0
  39. package/dist/src/core/model-policy/resolve.js +96 -6
  40. package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
  41. package/dist/src/core/model-policy/setup-readiness.js +77 -0
  42. package/dist/src/providers/index.js +3 -1
  43. package/dist/tests/commands/doctor-routing.test.js +22 -0
  44. package/dist/tests/commands/doctor.test.js +5 -0
  45. package/dist/tests/commands/hooks/install.test.js +6 -0
  46. package/dist/tests/commands/hooks/resync.test.js +3 -0
  47. package/dist/tests/commands/hooks/status.test.js +12 -1
  48. package/dist/tests/commands/job/routing-file.test.js +32 -0
  49. package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
  50. package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
  51. package/dist/tests/commands/model-policy/index.test.js +179 -0
  52. package/dist/tests/commands/preflight/preflight.test.js +65 -0
  53. package/dist/tests/commands/watch/apply.test.js +84 -0
  54. package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
  55. package/dist/tests/commands/watch/request-recovery.test.js +253 -0
  56. package/dist/tests/core/admission/index.test.js +21 -0
  57. package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
  58. package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
  59. package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
  60. package/dist/tests/core/model-policy/event-store.test.js +151 -0
  61. package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
  62. package/dist/tests/core/model-policy/journal.test.js +112 -1
  63. package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
  64. package/dist/tests/core/model-policy/machine-key.test.js +38 -0
  65. package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
  66. package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
  67. package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
  68. package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
  69. package/dist/tests/core/model-policy/native-claude.test.js +21 -0
  70. package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
  71. package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
  72. package/dist/tests/core/model-policy/native-codex.test.js +133 -0
  73. package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
  74. package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
  75. package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
  76. package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
  77. package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
  78. package/dist/tests/providers/index.test.js +1 -1
  79. package/package.json +2 -2
  80. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  81. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  82. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  83. 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: next.profile, resolved: resolved.selection, policyDigest: resolved.policyDigest, capabilityDigest: resolved.capabilityDigest, outcome: resolved.outcome, unavailableEvidence: resolved.unavailableEvidence }, custodyHandoff: { kind: 'routing-reserve', obligationId: lineage.obligationId, lineageId: lineage.id } };
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 = () => { if (!options.runtimeKind || !options.runtimeVersion || !options.accountScopeDigest)
294
- return undefined; const runtime = (0, capabilities_1.validateRuntimeKey)({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest }); const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(options.cwd); const at = deps.routingNow?.() ?? new Date(); if (!(at instanceof Date) || !Number.isFinite(at.getTime()))
295
- throw new Error('routingNow must return a finite Date'); const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, at); return { runtime, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, now: at }; };
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 observed = (0, journal_1.observeRoutingAttempt)(s, { attemptId: p.attemptId, nativeAgentId: p.nativeAgentId, ...(p.observed === undefined ? {} : { observed: p.observed }), ...(p.unavailableReason === undefined ? {} : { unavailableReason: p.unavailableReason }) }, p.at ?? now());
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.requestProblems.length > 0)
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
+ }