agentic-workflow-manager 9.9.0 → 9.10.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.
@@ -6,6 +6,7 @@ const providers_1 = require("../../providers");
6
6
  const store_1 = require("../../core/model-policy/store");
7
7
  const validate_1 = require("../../core/model-policy/validate");
8
8
  const capabilities_1 = require("../../core/model-policy/capabilities");
9
+ const digest_1 = require("../../core/model-policy/digest");
9
10
  const roles = ['implementer', 'specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'];
10
11
  const implementerProfiles = ['mechanical', 'integration', 'judgment'];
11
12
  const contract = { schema: 'routing-protocol/v1', supportedPlanSchemas: ['compact-slices/v1', 'compact-slices/v2'], roles, implementerProfiles, limits: { maxFileBytes: validate_1.MAX_POLICY_BYTES, maxMappings: validate_1.MAX_MAPPINGS, maxIdentifierChars: 128, maxDiagnostics: 20, maxDiagnosticChars: 4096 } };
@@ -20,9 +21,21 @@ function registerModelPolicyCommand(program, deps = {}) {
20
21
  const read = deps.readEffectivePolicy ?? store_1.readEffectivePolicy;
21
22
  const approveReceipt = deps.approveCapabilities ?? capabilities_1.approveCapabilities;
22
23
  const readReceipt = deps.readCapabilities ?? capabilities_1.readCapabilities;
24
+ const digest = deps.candidateDigest ?? digest_1.candidateDigest;
23
25
  const command = program.command('model-policy').description('inspect and explicitly approve portable routing policy');
24
26
  command.command('contract').option('--cwd <path>').option('--json').action((options) => { if (options.cwd !== undefined)
25
27
  text(options.cwd, '--cwd'); output({ ...contract, protocolDigest }, options.json === true); });
28
+ // Approval demands --expected-digest so that it stays an explicit operator
29
+ // act. Without a way to observe the digest first, that safeguard blocked the
30
+ // legitimate path instead of gating it, and the only way through was to
31
+ // reimplement canonicalisation outside the CLI — the second parser the
32
+ // admission contract forbids. This discloses the digest and approves nothing.
33
+ command.command('digest').description('compute the canonical digest of a candidate policy or capability receipt without approving it').requiredOption('--file <path>').option('--cwd <path>').option('--json').action((options) => {
34
+ text(options.file, '--file');
35
+ if (options.cwd !== undefined)
36
+ text(options.cwd, '--cwd');
37
+ output(digest({ file: options.file, cwd: options.cwd ?? process.cwd() }), options.json === true);
38
+ });
26
39
  command.command('approve').requiredOption('--file <path>').requiredOption('--scope <scope>').requiredOption('--expected-digest <sha>').option('--replace-digest <sha>').option('--cwd <path>').option('--json').action((options) => {
27
40
  text(options.file, '--file');
28
41
  text(options.scope, '--scope');
@@ -37,7 +50,15 @@ function registerModelPolicyCommand(program, deps = {}) {
37
50
  const policy = approve({ file: options.file, scope: options.scope, cwd: options.cwd ?? process.cwd(), expectedDigest: options.expectedDigest, replaceDigest: options.replaceDigest });
38
51
  output({ state: 'approved', policy }, options.json === true);
39
52
  });
40
- command.command('capabilities approve').requiredOption('--file <path>').requiredOption('--expected-digest <sha>').option('--replace-digest <sha>').option('--cwd <path>').option('--json').action((options) => {
53
+ // `command('capabilities approve')` registered ONE command named
54
+ // `capabilities` with a required positional `<approve>`, so Commander called
55
+ // the action as (positional, options, command) and the handler's first
56
+ // parameter — read as `options` — was the string 'approve'. Every invocation
57
+ // therefore died on `--file requires a non-empty value`, and the path could
58
+ // never approve anything. A real subcommand group keeps the invocation
59
+ // spelling identical and gives the action the options object it expects.
60
+ const capabilities = command.command('capabilities').description('inspect and explicitly approve runtime capability attestations');
61
+ capabilities.command('approve').requiredOption('--file <path>').requiredOption('--expected-digest <sha>').option('--replace-digest <sha>').option('--cwd <path>').option('--json').action((options) => {
41
62
  text(options.file, '--file');
42
63
  text(options.expectedDigest, '--expected-digest');
43
64
  if (options.replaceDigest !== undefined)
@@ -183,10 +183,39 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
183
183
  if (!roles.includes(options.role))
184
184
  throw new Error('--role is invalid');
185
185
  const runtime = (0, capabilities_1.validateRuntimeKey)({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest });
186
- 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 }) : (() => { const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(cwd); const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date()); if (!options.lineage)
187
- return (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 }); const state = journalObservation(cwd).journalState; const lineage = state.implementationLineages.find(candidate => candidate.id === options.lineage); const next = (0, journal_1.resolveLineageEscalation)(state, options.lineage, lineage.initialProfile); const resolved = (0, resolve_1.resolveSelection)({ role: 'implementer', requestedProfile: next.profile, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date() }); if (resolved.state === 'blocked')
188
- return resolved; const highRequiresFull = next.effort !== 'high' || (policy.state === 'approved' && (() => { const full = policy.policy.content.mappings.find(row => row.target === runtime.target && row.runtimeKind === runtime.kind)?.fullCapability; return full?.selector.kind === resolved.selection.selector.kind && full.selector.id === resolved.selection.selector.id && full.effort.kind === 'explicit' && full.effort.value === 'high'; })()); if (resolved.selection.effort.kind !== 'explicit' || resolved.selection.effort.value !== next.effort || !highRequiresFull)
189
- return { state: 'blocked', diagnostics: [{ code: 'ROUTING_ESCALATION_EFFORT_UNAVAILABLE', message: 'The approved selected effort does not match the next lineage escalation.' }] }; 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 } }; })();
186
+ 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 }) : (() => {
187
+ const policy = (deps.readEffectivePolicy ?? store_2.readEffectivePolicy)(cwd);
188
+ const capabilities = (deps.readCapabilities ?? capabilities_1.readCapabilities)(runtime, new Date());
189
+ if (!options.lineage) {
190
+ 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 });
191
+ // The consumer forwards THIS envelope to `job routing-reserve`, which
192
+ // is what creates the lineage the --lineage escalation path below
193
+ // then reads. Emitting it only there made the first attempt of any
194
+ // lineage unreservable: envelope <= lineage <= reserve <= envelope.
195
+ // Every field comes from values already in hand, so the CLI stays the
196
+ // sole author and the consumer never assembles a protocol document.
197
+ // No custodyHandoff here on purpose: the obligation and lineage ids
198
+ // are the caller's to choose on a first attempt, and inventing them
199
+ // would put identity the CLI cannot know into custody.
200
+ if (first.state !== 'resolved' || report.schema !== 'compact-slices/v2' || report.executionDigest === undefined)
201
+ return first;
202
+ const slice = report.manifest.slices.find((candidate) => candidate.id === options.slice);
203
+ const requestedProfile = options.role === 'implementer' ? slice?.implementerProfile : 'full';
204
+ if (requestedProfile === undefined)
205
+ return first;
206
+ 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 } };
207
+ }
208
+ const state = journalObservation(cwd).journalState;
209
+ const lineage = state.implementationLineages.find(candidate => candidate.id === options.lineage);
210
+ const next = (0, journal_1.resolveLineageEscalation)(state, options.lineage, lineage.initialProfile);
211
+ const resolved = (0, resolve_1.resolveSelection)({ role: 'implementer', requestedProfile: next.profile, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined, runtime, now: new Date() });
212
+ if (resolved.state === 'blocked')
213
+ return resolved;
214
+ const highRequiresFull = next.effort !== 'high' || (policy.state === 'approved' && (() => { const full = policy.policy.content.mappings.find(row => row.target === runtime.target && row.runtimeKind === runtime.kind)?.fullCapability; return full?.selector.kind === resolved.selection.selector.kind && full.selector.id === resolved.selection.selector.id && full.effort.kind === 'explicit' && full.effort.value === 'high'; })());
215
+ if (resolved.selection.effort.kind !== 'explicit' || resolved.selection.effort.value !== next.effort || !highRequiresFull)
216
+ return { state: 'blocked', diagnostics: [{ code: 'ROUTING_ESCALATION_EFFORT_UNAVAILABLE', message: 'The approved selected effort does not match the next lineage escalation.' }] };
217
+ 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 } };
218
+ })();
190
219
  process.stdout.write(options.json ? `${JSON.stringify(result)}\n` : `Plan routing: ${result.state}\n`);
191
220
  if (result.state === 'blocked')
192
221
  process.exitCode = 2;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Supervisor = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
3
+ exports.Supervisor = exports.MAX_SENSOR_DEFERRALS = exports.DEFAULT_SUPERVISOR_CONFIG = void 0;
4
4
  exports.runSupervisorLoop = runSupervisorLoop;
5
5
  const store_1 = require("../../core/journal/store");
6
6
  const fingerprint_1 = require("../../core/journal/fingerprint");
@@ -120,6 +120,15 @@ exports.DEFAULT_SUPERVISOR_CONFIG = {
120
120
  jobStallObservationMs: 5 * 60000, // R3.5 default: mismo orden de magnitud que heartbeatTimeoutMs, concern independiente
121
121
  maxParallelTracks: 1, // overridden en tiempo de ejecución con loadDefaultParallelism() (watch/index.ts)
122
122
  };
123
+ // 'frozen' (R5.2/R6.3, Task 10): SOLO puede ocurrir en el journal de un
124
+ // TRACK individual (nunca en el del plan) — el track terminó, con las 6
125
+ // observaciones demostrables (cero jobs vivos, gate local verde, worktree
126
+ // limpio, generación propia terminada con identidad confirmada), de
127
+ // atender un `track-freeze-request` pedido por el supervisor del plan.
128
+ /** How many consecutive ticks an inconclusive sensor verdict may defer dispatch
129
+ * before the supervisor says so out loud. Small: the deferral covers a verdict
130
+ * still settling, not one that never will. */
131
+ exports.MAX_SENSOR_DEFERRALS = 3;
123
132
  const LIVE = ['received', 'spawn-intent', 'claimed', 'running', 'cancel-requested'];
124
133
  function recoveryWhitelistBlocker(state) {
125
134
  if (state.cycle.status === 'BLOCKED')
@@ -149,6 +158,10 @@ class Supervisor {
149
158
  dispatchAdmission;
150
159
  backoff = new generations_1.Backoff();
151
160
  relaunchNotBefore = 0;
161
+ /** Consecutive ticks deferred on an inconclusive sensor verdict. Bounded:
162
+ * the deferral exists for a verdict that is still settling, and a verdict
163
+ * that never settles must become visible rather than loop forever. */
164
+ sensorDeferrals = 0;
152
165
  lastActivity = null;
153
166
  lastGenerationToken = null;
154
167
  trackRuntime;
@@ -226,12 +239,30 @@ class Supervisor {
226
239
  // the already-started controller is still settling. It never
227
240
  // authorizes dispatch; retry the read-only admission next tick
228
241
  // instead of permanently custodying a healthy cycle.
242
+ //
243
+ // Bounded, because the verdict is not always transient: a
244
+ // registry whose sensors are all disabled reports
245
+ // `not-certified` forever. Unbounded, this returned before
246
+ // `consumePendingRequests` below, so the supervisor ticked
247
+ // indefinitely applying nothing and writing nothing — no event,
248
+ // no state change, no exit. Refusing to progress is correct
249
+ // here; refusing invisibly is not. Consuming requests cannot be
250
+ // hoisted above this gate to compensate: applying a job-request
251
+ // creates a Job that runnerTick can start, which is the dispatch
252
+ // this admission exists to authorize.
229
253
  if (admission.planState === 'valid' && admission.currentness === 'current'
230
- && admission.sensors === 'not-certified' && admission.journal === 'not-required')
231
- return 'continue';
254
+ && admission.sensors === 'not-certified' && admission.journal === 'not-required') {
255
+ this.sensorDeferrals += 1;
256
+ if (this.sensorDeferrals <= exports.MAX_SENSOR_DEFERRALS)
257
+ return 'continue';
258
+ (0, generations_1.enterCustody)(this.repoRoot, this.branch, `veredicto de sensores no concluyente tras ${this.sensorDeferrals} ticks: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
259
+ return 'custody';
260
+ }
232
261
  (0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión compacta desatendida bloqueada antes de dispatch: ${admission.diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join('; ')}`);
233
262
  return 'custody';
234
263
  }
264
+ // The verdict settled, which is exactly what the deferral waited for.
265
+ this.sensorDeferrals = 0;
235
266
  }
236
267
  let recoveryResumeAction;
237
268
  // Schema-2 custody is reconciled before any controller launch. This is
@@ -0,0 +1,35 @@
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.candidateDigest = candidateDigest;
7
+ const path_1 = __importDefault(require("path"));
8
+ const json_1 = require("../plan/json");
9
+ const capabilities_1 = require("./capabilities");
10
+ const canonical_1 = require("./canonical");
11
+ const store_1 = require("./store");
12
+ const validate_1 = require("./validate");
13
+ const CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
14
+ function assertPath(value, label) {
15
+ if (typeof value !== 'string' || value.length === 0 || value.length > 4096 || CONTROL.test(value))
16
+ throw new Error(`${label} must be a bounded path without control characters`);
17
+ }
18
+ function candidateDigest(input) {
19
+ if (!input || typeof input !== 'object')
20
+ throw new Error('digest input is required');
21
+ assertPath(input.file, 'file');
22
+ assertPath(input.cwd, 'cwd');
23
+ const raw = (0, store_1.readBoundedCandidate)(path_1.default.resolve(path_1.default.resolve(input.cwd), input.file));
24
+ if (raw === null)
25
+ throw new Error('digest candidate is absent');
26
+ const parsed = (0, json_1.parseJsonNoDuplicate)(raw);
27
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
28
+ throw new Error('digest candidate must be a JSON object');
29
+ const schema = parsed.schema;
30
+ if (schema === 'model-policy/v1')
31
+ return { schema, digest: (0, canonical_1.canonicalPolicyDigest)((0, validate_1.validatePolicyContent)(parsed)) };
32
+ if (schema === 'routing-capabilities/v1')
33
+ return { schema, digest: (0, capabilities_1.capabilityReceiptDigest)((0, capabilities_1.validateCapabilityReceipt)(parsed)) };
34
+ throw new Error('digest candidate must declare schema model-policy/v1 or routing-capabilities/v1');
35
+ }
@@ -79,33 +79,72 @@ function resolveSelection(input) {
79
79
  let selection = wanted;
80
80
  const unavailableEvidence = [];
81
81
  let outcome = 'native';
82
- const canDegrade = (capability, allowed, code) => {
83
- const state = capabilities.capabilities[capability];
82
+ // Surrendering the model means the runtime picks, so the only safe landing
83
+ // place is the attested runtime default, and it must be the approved full
84
+ // capability. Surrendering the EFFORT is a narrower loss and is handled
85
+ // separately below: collapsing to fullCapability there would throw away a
86
+ // model override the runtime demonstrably honours.
87
+ const degradeModel = () => {
88
+ const state = capabilities.capabilities.modelOverride;
84
89
  if (state === 'supported')
85
90
  return undefined;
86
91
  if (state === 'unverified')
87
- return blocked(code, `${capability} is unverified.`);
88
- if (!allowed || !capabilities.runtimeDefaultSelection || !equal(capabilities.runtimeDefaultSelection, mapping.fullCapability))
89
- return blocked(code, `${capability} is unavailable and no matching approved full default is attested.`);
92
+ return blocked('ROUTING_MODEL_OVERRIDE_UNAVAILABLE', 'modelOverride is unverified.');
93
+ if (!mapping.degradation.allowMissingModelOverride || !capabilities.runtimeDefaultSelection || !equal(capabilities.runtimeDefaultSelection, mapping.fullCapability))
94
+ return blocked('ROUTING_MODEL_OVERRIDE_UNAVAILABLE', 'modelOverride is unavailable and no matching approved full default is attested.');
90
95
  selection = mapping.fullCapability;
91
96
  outcome = 'degraded';
92
- unavailableEvidence.push(capability);
97
+ unavailableEvidence.push('modelOverride');
93
98
  return undefined;
94
99
  };
95
- const model = canDegrade('modelOverride', mapping.degradation.allowMissingModelOverride, 'ROUTING_MODEL_OVERRIDE_UNAVAILABLE');
100
+ // Degrading effort surrenders the effort component ONLY, keeping the routed
101
+ // model. On a runtime where the model can be overridden but reasoning effort
102
+ // cannot — effort belongs to the agent definition, not the invocation — the
103
+ // alternative was to route every profile to the full-capability model, so a
104
+ // mechanical slice would quietly run the judgment model while the diagnostic
105
+ // spoke only of effort. That is the silent defaulting the consumer reference
106
+ // forbids. The model at runtime-default effort must itself be attested.
107
+ const degradeEffort = () => {
108
+ const state = capabilities.capabilities.effortOverride;
109
+ if (state === 'supported')
110
+ return undefined;
111
+ if (state === 'unverified')
112
+ return blocked('ROUTING_EFFORT_OVERRIDE_UNAVAILABLE', 'effortOverride is unverified.');
113
+ if (!mapping.degradation.allowMissingEffortOverride)
114
+ return blocked('ROUTING_EFFORT_OVERRIDE_UNAVAILABLE', 'effortOverride is unavailable and the mapping does not allow degrading it.');
115
+ const degraded = { selector: selection.selector, effort: { kind: 'runtime-default' } };
116
+ if (!capabilities.runtimeDefaultSelection || !capabilities.availableSelections.some(candidate => equal(candidate, degraded)))
117
+ return blocked('ROUTING_EFFORT_OVERRIDE_UNAVAILABLE', 'effortOverride is unavailable and the routed model at runtime-default effort is not attested.');
118
+ selection = degraded;
119
+ outcome = 'degraded';
120
+ unavailableEvidence.push('effortOverride');
121
+ return undefined;
122
+ };
123
+ const model = degradeModel();
96
124
  if (model)
97
125
  return model;
98
- if (!capabilities.availableSelections.some(candidate => equal(candidate, selection)))
99
- return blocked('ROUTING_SELECTION_UNAVAILABLE', 'Approved selection is not currently available; no substitute is allowed.');
100
- if (selection.effort.kind === 'runtime-default') {
101
- if (!capabilities.runtimeDefaultSelection || !equal(capabilities.runtimeDefaultSelection, selection))
102
- return blocked('ROUTING_SELECTION_UNAVAILABLE', 'Runtime-default selection is not explicitly attested.');
103
- }
104
- else {
105
- const effort = canDegrade('effortOverride', mapping.degradation.allowMissingEffortOverride, 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE');
126
+ if (selection.effort.kind === 'explicit') {
127
+ const effort = degradeEffort();
106
128
  if (effort)
107
129
  return effort;
108
130
  }
131
+ // availableSelections is the receipt's attestation of what can actually be
132
+ // chosen, so it is checked on the FINAL selection, after any degradation
133
+ // rewrote it. Checking it first forced a runtime that cannot honour explicit
134
+ // effort to attest selections it could never run, purely to reach the
135
+ // degradation that would then discard them — the honest receipt was the one
136
+ // that failed.
137
+ if (!capabilities.availableSelections.some(candidate => equal(candidate, selection)))
138
+ return blocked('ROUTING_SELECTION_UNAVAILABLE', 'Approved selection is not currently available; no substitute is allowed.');
139
+ // A runtime-default effort is only meaningful if the receipt declares what the
140
+ // runtime default is. It need NOT equal this selection: requiring that made
141
+ // every per-profile selection unreachable on a runtime whose default model
142
+ // differs, even with modelOverride attested supported and the selection
143
+ // itself attested available. Attesting `haiku at runtime-default effort` is
144
+ // precisely the statement that the model may be overridden while effort is
145
+ // left alone.
146
+ if (selection.effort.kind === 'runtime-default' && !capabilities.runtimeDefaultSelection)
147
+ return blocked('ROUTING_SELECTION_UNAVAILABLE', 'Runtime-default selection is not explicitly attested.');
109
148
  if (capabilities.capabilities.observedModelEvidence !== 'supported') {
110
149
  if (!mapping.degradation.allowMissingObservedIdentity)
111
150
  return blocked('ROUTING_OBSERVED_IDENTITY_UNAVAILABLE', 'Observed model identity is required and unavailable.');
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readBoundedCandidate = readBoundedCandidate;
6
7
  exports.readEffectivePolicy = readEffectivePolicy;
7
8
  exports.approvePolicyWithBoundary = approvePolicyWithBoundary;
8
9
  exports.approvePolicy = approvePolicy;
@@ -45,7 +46,13 @@ catch (error) {
45
46
  if (error.code !== 'ENOENT')
46
47
  throw error;
47
48
  } }
48
- function readBounded(file) {
49
+ /**
50
+ * Bounded, symlink-refusing, identity-fenced read of a candidate or stored file.
51
+ * Exported so the read-only digest path observes a file under exactly the same
52
+ * constraints the approval path does, rather than growing a second reader that
53
+ * could drift from it.
54
+ */
55
+ function readBoundedCandidate(file) {
49
56
  assertSafeParents(file);
50
57
  let inspected;
51
58
  try {
@@ -106,7 +113,7 @@ function readEffectivePolicy(cwd) {
106
113
  assertPath(cwd, 'cwd');
107
114
  const project = (0, paths_1.projectPolicyPath)(cwd);
108
115
  try {
109
- const raw = readBounded(project);
116
+ const raw = readBoundedCandidate(project);
110
117
  if (raw !== null)
111
118
  return { state: 'approved', provenance: 'project', policy: parseApproved(raw) };
112
119
  }
@@ -115,7 +122,7 @@ function readEffectivePolicy(cwd) {
115
122
  }
116
123
  const user = (0, paths_1.userPolicyPath)();
117
124
  try {
118
- const raw = readBounded(user);
125
+ const raw = readBoundedCandidate(user);
119
126
  if (raw === null)
120
127
  return { state: 'absent' };
121
128
  return { state: 'approved', provenance: 'user', policy: parseApproved(raw) };
@@ -134,7 +141,7 @@ function approvePolicyWithBoundary(input, boundary) {
134
141
  if (input.replaceDigest !== undefined)
135
142
  (0, validate_1.assertDigest)(input.replaceDigest, 'replaceDigest');
136
143
  const cwd = path_1.default.resolve(input.cwd);
137
- const candidateRaw = readBounded(path_1.default.resolve(cwd, input.file));
144
+ const candidateRaw = readBoundedCandidate(path_1.default.resolve(cwd, input.file));
138
145
  if (candidateRaw === null)
139
146
  throw new Error('policy candidate is absent');
140
147
  const content = (0, validate_1.validatePolicyContent)((0, json_1.parseJsonNoDuplicate)(candidateRaw));
@@ -147,7 +154,7 @@ function approvePolicyWithBoundary(input, boundary) {
147
154
  assertRegularOrAbsent(target);
148
155
  const unlock = lock(target);
149
156
  try {
150
- const existingRaw = readBounded(target);
157
+ const existingRaw = readBoundedCandidate(target);
151
158
  const existing = existingRaw === null ? null : parseApproved(existingRaw);
152
159
  if (existing === null && input.replaceDigest !== undefined)
153
160
  throw new Error('replacement requires an existing predecessor');
@@ -47,11 +47,71 @@ describe('model-policy command', () => {
47
47
  expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({ policy: { state: 'absent' }, capability: { state: 'stale' } });
48
48
  expect(process.exitCode).toBe(2);
49
49
  });
50
+ it('discloses the canonical digest through a read-only subcommand', async () => {
51
+ const candidateDigest = jest.fn(() => ({ schema: 'model-policy/v1', digest: 'b'.repeat(64) }));
52
+ const program = new commander_1.Command();
53
+ program.exitOverride();
54
+ program.configureOutput({ writeErr: () => undefined });
55
+ (0, model_policy_1.registerModelPolicyCommand)(program, { candidateDigest });
56
+ await program.parseAsync(['node', 'awm', 'model-policy', 'digest', '--file', 'policy.json', '--cwd', 'fixture-root', '--json']);
57
+ expect(candidateDigest).toHaveBeenCalledWith({ file: 'policy.json', cwd: 'fixture-root' });
58
+ expect(JSON.parse(String(out.mock.calls[0][0]))).toEqual({ schema: 'model-policy/v1', digest: 'b'.repeat(64) });
59
+ });
60
+ it('takes no approval arguments on digest, so it cannot approve by accident', async () => {
61
+ const candidateDigest = jest.fn(() => ({ schema: 'model-policy/v1', digest: 'b'.repeat(64) }));
62
+ const approvePolicy = jest.fn();
63
+ const approveCapabilities = jest.fn();
64
+ const program = new commander_1.Command();
65
+ program.exitOverride();
66
+ program.configureOutput({ writeErr: () => undefined });
67
+ (0, model_policy_1.registerModelPolicyCommand)(program, { candidateDigest, approvePolicy: approvePolicy, approveCapabilities: approveCapabilities });
68
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'digest', '--file', 'policy.json', '--expected-digest', 'a'.repeat(64)])).rejects.toThrow();
69
+ expect(approvePolicy).not.toHaveBeenCalled();
70
+ expect(approveCapabilities).not.toHaveBeenCalled();
71
+ });
72
+ it('rejects a missing or malformed --file on digest instead of digesting the flag', async () => {
73
+ const candidateDigest = jest.fn(() => ({ schema: 'model-policy/v1', digest: 'b'.repeat(64) }));
74
+ const program = new commander_1.Command();
75
+ program.exitOverride();
76
+ program.configureOutput({ writeErr: () => undefined });
77
+ (0, model_policy_1.registerModelPolicyCommand)(program, { candidateDigest });
78
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'digest'])).rejects.toThrow();
79
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'digest', '--file', '--cwd'])).rejects.toThrow(/requires a non-empty value/);
80
+ expect(candidateDigest).not.toHaveBeenCalled();
81
+ });
82
+ // This positive case is what the negative guard below could never establish.
83
+ // While `capabilities approve` was registered as one command with a
84
+ // positional `<approve>`, Commander handed the action (positional, options),
85
+ // so `--file` read as undefined and EVERY invocation — well-formed ones
86
+ // included — died on 'requires a non-empty value'. A suite that only
87
+ // asserted that rejection was green against a command that could not
88
+ // approve at all.
89
+ it('reaches the capability approval with parsed options, not a positional', async () => {
90
+ const approveCapabilities = jest.fn(() => ({ schema: 'routing-capabilities/v1' }));
91
+ const program = new commander_1.Command();
92
+ program.exitOverride();
93
+ program.configureOutput({ writeErr: () => undefined });
94
+ (0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: approveCapabilities });
95
+ await program.parseAsync(['node', 'awm', 'model-policy', 'capabilities', 'approve', '--file', 'receipt.json', '--expected-digest', 'a'.repeat(64), '--cwd', 'fixture-root', '--json']);
96
+ expect(approveCapabilities).toHaveBeenCalledWith({ file: 'receipt.json', cwd: 'fixture-root', expectedDigest: 'a'.repeat(64), replaceDigest: undefined });
97
+ expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({ state: 'approved' });
98
+ });
99
+ it('rejects an unknown capabilities subcommand instead of treating it as an approval', async () => {
100
+ const approveCapabilities = jest.fn();
101
+ const program = new commander_1.Command();
102
+ program.exitOverride();
103
+ program.configureOutput({ writeErr: () => undefined });
104
+ (0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: approveCapabilities });
105
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'capabilities', 'revoke', '--file', 'receipt.json', '--expected-digest', 'a'.repeat(64)])).rejects.toThrow();
106
+ expect(approveCapabilities).not.toHaveBeenCalled();
107
+ });
50
108
  it('rejects an empty replacement digest for capability approval at the command boundary', async () => {
109
+ const approveCapabilities = jest.fn();
51
110
  const program = new commander_1.Command();
52
111
  program.exitOverride();
53
112
  program.configureOutput({ writeErr: () => undefined });
54
- (0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: jest.fn() });
113
+ (0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: approveCapabilities });
55
114
  await expect(program.parseAsync(['node', 'awm', 'model-policy', 'capabilities', 'approve', '--file', 'receipt.json', '--expected-digest', 'a'.repeat(64), '--replace-digest', ''])).rejects.toThrow(/requires a non-empty value/);
115
+ expect(approveCapabilities).not.toHaveBeenCalled();
56
116
  });
57
117
  });
@@ -13,12 +13,20 @@ const plan_1 = require("../../../src/commands/plan");
13
13
  const init_1 = require("../../../src/commands/watch/init");
14
14
  const providers_1 = require("../../../src/providers");
15
15
  const registry_1 = require("../../../src/core/renderers/registry");
16
+ const canonical_1 = require("../../../src/core/model-policy/canonical");
17
+ const capabilities_1 = require("../../../src/core/model-policy/capabilities");
18
+ const journal_1 = require("../../../src/core/model-policy/journal");
19
+ const types_1 = require("../../../src/core/journal/types");
16
20
  const stdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
17
21
  const repositoryRoot = path_1.default.resolve(__dirname, '../../../..');
18
22
  const approved = (0, validate_1.validatePlanFile)('docs/plans/2026-09-14-compact-only-bootstrap-plan.md', repositoryRoot);
19
23
  if (approved.state !== 'valid')
20
24
  throw new Error(`tracked bootstrap plan must validate before command tests: ${approved.state}`);
21
25
  const valid = approved;
26
+ const approvedV2 = (0, validate_1.validatePlanFile)('valid.md', path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v2'));
27
+ if (approvedV2.state !== 'valid')
28
+ throw new Error(`v2 fixture must validate before command tests: ${approvedV2.state}`);
29
+ const v2Valid = approvedV2;
22
30
  const invalid = {
23
31
  state: 'invalid', diagnostics: [{ code: 'PLAN_MARKERS', message: 'compact markers must occur once in order' }],
24
32
  };
@@ -60,6 +68,56 @@ describe('plan validate Commander wiring', () => {
60
68
  expect(policy).not.toHaveBeenCalled();
61
69
  expect(capabilities).not.toHaveBeenCalled();
62
70
  });
71
+ // The consumer reference says: call `plan resolve` for the exact role and
72
+ // local slice, then send ITS frozen envelope to `job routing-reserve`. The
73
+ // envelope was only ever built on the --lineage branch, which resolves the
74
+ // lineage out of journal state — and lineages are created by
75
+ // reserveRoutingAttempt, i.e. by routing-reserve itself. envelope <= lineage
76
+ // <= reserve <= envelope, so the first attempt of any lineage could never be
77
+ // reserved. This test goes from resolve output to a reserved attempt with no
78
+ // hand-built envelope literal, which is the only shape that can catch it:
79
+ // tests/commands/job/routing.test.ts passes a literal and so starts one step
80
+ // past the gap.
81
+ it('emits a reservable envelope on a first v2 attempt, closing the resolve to reserve path', async () => {
82
+ // A genuinely validated report: the resolver rejects any object that did
83
+ // not come out of validatePlanFile, so a spread-and-relabel fixture
84
+ // cannot reach this path at all.
85
+ const sliceId = 'S1';
86
+ const sel = { selector: { kind: 'model', id: 'gpt-5.6-luna' }, effort: { kind: 'explicit', value: 'medium' } };
87
+ const full = { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'high' } };
88
+ const sha = 'a'.repeat(64);
89
+ const content = { schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: { mechanical: sel, integration: sel, judgment: sel }, fullCapability: full, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } };
90
+ const readEffectivePolicy = () => ({ state: 'approved', provenance: 'user', policy: { schema: 'approved-model-policy/v1', content, contentDigest: (0, canonical_1.canonicalPolicyDigest)(content), approval: { approvedAt: '2026-09-19T00:00:00.000Z', approvalId: 'a1' }, lineage: { previousDigest: null } } });
91
+ const receipt = {
92
+ schema: 'routing-capabilities/v1',
93
+ runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha },
94
+ recordedAt: new Date(Date.now() - 60_000).toISOString(), expiresAt: new Date(Date.now() + 6 * 3600_000).toISOString(),
95
+ capabilities: { artifactDelivery: 'supported', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'supported', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' },
96
+ availableSelections: [sel, full], runtimeDefaultSelection: full,
97
+ evidence: ['modelOverride', 'effortOverride', 'observedModelEvidence', 'nativeSubagents', 'artifactDelivery', 'interactiveExecution', 'unattendedController'].map(capability => ({ capability, kind: 'native-control', receiptDigest: 'b'.repeat(64) })),
98
+ approval: { approvalId: 'r1', snapshotDigest: 'c'.repeat(64) },
99
+ };
100
+ const readCapabilities = () => ({ state: 'current', receipt, digest: (0, capabilities_1.capabilityReceiptDigest)(receipt) });
101
+ const program = new commander_1.Command();
102
+ program.exitOverride();
103
+ program.configureOutput({ writeErr: () => undefined });
104
+ (0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => v2Valid, readEffectivePolicy: readEffectivePolicy, readCapabilities: readCapabilities });
105
+ await program.parseAsync(['node', 'awm', 'plan', 'resolve', 'plans/r4.md', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', sha, '--role', 'implementer', '--slice', sliceId, '--cwd', repositoryRoot, '--json']);
106
+ const emitted = JSON.parse(String(stdoutWrite.mock.calls[stdoutWrite.mock.calls.length - 1][0]));
107
+ expect(emitted.state).toBe('resolved');
108
+ expect(emitted.envelope).toBeDefined();
109
+ // The CLI must be the sole author: the envelope has to satisfy the same
110
+ // predicate routing-reserve applies, with nothing filled in by the caller.
111
+ expect((0, types_1.isRoutingEnvelope)(emitted.envelope)).toBe(true);
112
+ expect(emitted.envelope).toMatchObject({ schema: 'routing-envelope/v1', role: 'implementer', sliceId, requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: sel });
113
+ expect(emitted.envelope.planDigest).toBe(v2Valid.planDigest);
114
+ expect(emitted.envelope.executionDigest).toBe(v2Valid.executionDigest);
115
+ // The point of the whole thing: that envelope reserves a first attempt,
116
+ // which is what creates the lineage the escalation path later needs.
117
+ const reserved = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'o1', lineageId: 'l1', envelope: emitted.envelope, fingerprint: 'e'.repeat(64) }, '2026-09-19T00:00:00.000Z');
118
+ expect(reserved.attemptId).toEqual(expect.any(String));
119
+ expect(reserved.state.implementationLineages).toEqual([expect.objectContaining({ id: 'l1', sliceId, initialProfile: 'mechanical' })]);
120
+ });
63
121
  it('emits deterministic human output for a valid compact plan', async () => {
64
122
  await commandFor(valid).parseAsync(['node', 'awm', 'plan', 'validate', 'plans/r4.md']);
65
123
  expect(String(stdoutWrite.mock.calls[0][0])).toBe('Plan validation: valid "plans/r4.md" (compact-slices/v1; 1 slices; 5 requirements; complete ownership)\n');
@@ -390,6 +390,44 @@ describe('supervisor loop', () => {
390
390
  await expect(new supervisor_1.Supervisor(repo, 'main', cfg, spy, undefined, admitted).tick()).rejects.toThrow(/rama|branch/i);
391
391
  expect(calls).toBe(0);
392
392
  });
393
+ // The sensor deferral at the top of tick() covers a first inconclusive read
394
+ // while a controller settles, and returns 'continue' so a transient does not
395
+ // custody a healthy cycle. It returns BEFORE consumePendingRequests, and
396
+ // `sensors: 'not-certified'` is not always transient: it is the steady state
397
+ // of any registry whose sensors are all disabled, which the baseline registry
398
+ // documents about itself. Unbounded, the supervisor ticked forever applying
399
+ // nothing and writing nothing — no event, no state change, no exit. Observed
400
+ // against published 9.10.0: a routing-reserve and an unrelated
401
+ // controller-heartbeat both sat unconsumed for ~36 ticks with appliedRequests
402
+ // empty and the cycle still IN_PROGRESS.
403
+ test('an inconclusive sensor verdict defers dispatch a bounded number of ticks, then says so', async () => {
404
+ const repo = setupRepo();
405
+ initUnattendedFixture(repo);
406
+ (0, generations_1.beginGeneration)(repo, 'main');
407
+ const token = (0, generations_1.activeGeneration)((0, store_1.readJournal)(repo, 'main').state).token;
408
+ (0, request_1.requestJob)(repo, 'main', token, ['node', '-e', 'process.exit(0)'], [], '.');
409
+ // Exactly what `awm plan admit --require-current --verify-sensors` returns
410
+ // for that registry: valid plan, current contracts, sensors not certified.
411
+ // Every field matches the deferral condition.
412
+ const notCertified = async () => ({
413
+ state: 'blocked', planState: 'valid', executionMode: 'desatendido',
414
+ journal: 'not-required', currentness: 'current', sensors: 'not-certified',
415
+ diagnostics: [{ code: 'ADMISSION_SENSORS_BLOCKED', message: 'sensors not certified' }],
416
+ });
417
+ let spawned = 0;
418
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawned += 1; }, undefined, notCertified);
419
+ for (let i = 0; i < supervisor_1.MAX_SENSOR_DEFERRALS; i += 1) {
420
+ expect(await sup.tick()).toBe('continue');
421
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
422
+ }
423
+ // The verdict never settled, so the supervisor must stop pretending it might.
424
+ expect(await sup.tick()).toBe('custody');
425
+ const state = (0, store_1.readJournal)(repo, 'main').state;
426
+ expect(state.cycle.status).toBe('BLOCKED');
427
+ expect(state.cycle.blockedReason).toMatch(/sensor/i);
428
+ // Deferring dispatch was right throughout, and stays right.
429
+ expect(spawned).toBe(0);
430
+ });
393
431
  test('una admisión compacta bloqueada no lanza controlador ni wrappers', async () => {
394
432
  initUnattendedFixture(repo);
395
433
  const state = (0, store_1.readJournal)(repo, 'main').state;
@@ -0,0 +1,117 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const digest_1 = require("../../../src/core/model-policy/digest");
10
+ const store_1 = require("../../../src/core/model-policy/store");
11
+ const capabilities_1 = require("../../../src/core/model-policy/capabilities");
12
+ const canonical_1 = require("../../../src/core/model-policy/canonical");
13
+ const capabilities_2 = require("../../../src/core/model-policy/capabilities");
14
+ const sha = 'a'.repeat(64);
15
+ const policy = () => ({ schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: {
16
+ mechanical: { selector: { kind: 'model', id: 'gpt-5.6-luna' }, effort: { kind: 'explicit', value: 'medium' } },
17
+ integration: { selector: { kind: 'model', id: 'gpt-5.6-terra' }, effort: { kind: 'explicit', value: 'medium' } },
18
+ judgment: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'medium' } },
19
+ }, fullCapability: { selector: { kind: 'model', id: 'gpt-5.6-sol' }, effort: { kind: 'explicit', value: 'high' } }, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } });
20
+ const receipt = () => ({
21
+ schema: 'routing-capabilities/v1',
22
+ runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha },
23
+ recordedAt: '2026-09-17T00:00:00.000Z', expiresAt: '2026-09-18T00:00:00.000Z',
24
+ capabilities: { artifactDelivery: 'unverified', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'unverified', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' },
25
+ availableSelections: [{ selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } }],
26
+ runtimeDefaultSelection: { selector: { kind: 'model', id: 'full' }, effort: { kind: 'explicit', value: 'high' } },
27
+ evidence: ['interactiveExecution', 'unattendedController', 'modelOverride', 'effortOverride', 'observedModelEvidence'].map(capability => ({ capability, kind: 'native-control', receiptDigest: sha })),
28
+ approval: { approvalId: 'approval-1', snapshotDigest: sha },
29
+ });
30
+ describe('candidateDigest', () => {
31
+ let cwd;
32
+ const home = process.env.HOME;
33
+ const awmHome = process.env.AWM_HOME;
34
+ beforeEach(() => { cwd = fs_1.default.mkdtempSync(path_1.default.join(fs_1.default.realpathSync(os_1.default.tmpdir()), 'awm-digest-')); process.env.HOME = cwd; process.env.AWM_HOME = path_1.default.join(cwd, '.awm'); });
35
+ afterEach(() => { fs_1.default.rmSync(cwd, { recursive: true, force: true }); if (home === undefined)
36
+ delete process.env.HOME;
37
+ else
38
+ process.env.HOME = home; if (awmHome === undefined)
39
+ delete process.env.AWM_HOME;
40
+ else
41
+ process.env.AWM_HOME = awmHome; });
42
+ const write = (name, value) => { const file = path_1.default.join(cwd, name); fs_1.default.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`); return name; };
43
+ it('emits the digest the policy approval path will demand, not a second opinion', () => {
44
+ const file = write('policy.json', policy());
45
+ expect((0, digest_1.candidateDigest)({ file, cwd })).toEqual({ schema: 'model-policy/v1', digest: (0, canonical_1.canonicalPolicyDigest)(policy()) });
46
+ });
47
+ it('emits the digest the capability approval path will demand', () => {
48
+ const file = write('receipt.json', receipt());
49
+ expect((0, digest_1.candidateDigest)({ file, cwd })).toEqual({ schema: 'routing-capabilities/v1', digest: (0, capabilities_2.capabilityReceiptDigest)(receipt()) });
50
+ });
51
+ it('is insensitive to key order and whitespace, because the digest is over the canonical form', () => {
52
+ const content = policy();
53
+ const reordered = { implementationBudget: content.implementationBudget, mappings: content.mappings, schema: content.schema };
54
+ fs_1.default.writeFileSync(path_1.default.join(cwd, 'a.json'), JSON.stringify(content));
55
+ fs_1.default.writeFileSync(path_1.default.join(cwd, 'b.json'), JSON.stringify(reordered, null, 4));
56
+ expect((0, digest_1.candidateDigest)({ file: 'a.json', cwd }).digest).toBe((0, digest_1.candidateDigest)({ file: 'b.json', cwd }).digest);
57
+ });
58
+ it('refuses a document whose declared schema it does not own, instead of guessing', () => {
59
+ const file = write('other.json', { schema: 'approved-model-policy/v1' });
60
+ expect(() => (0, digest_1.candidateDigest)({ file, cwd })).toThrow(/schema model-policy\/v1 or routing-capabilities\/v1/);
61
+ });
62
+ it('refuses an invalid document rather than emitting a digest approval would reject', () => {
63
+ const broken = policy();
64
+ delete broken.implementationBudget;
65
+ const file = write('broken.json', broken);
66
+ expect(() => (0, digest_1.candidateDigest)({ file, cwd })).toThrow();
67
+ });
68
+ it('reports an absent candidate loudly', () => {
69
+ expect(() => (0, digest_1.candidateDigest)({ file: 'missing.json', cwd })).toThrow(/absent/);
70
+ });
71
+ it('refuses a symlinked candidate, matching the approval path', () => {
72
+ write('policy.json', policy());
73
+ fs_1.default.symlinkSync(path_1.default.join(cwd, 'policy.json'), path_1.default.join(cwd, 'link.json'));
74
+ expect(() => (0, digest_1.candidateDigest)({ file: 'link.json', cwd })).toThrow(/unsafe/);
75
+ });
76
+ it('rejects malformed input instead of returning undefined', () => {
77
+ expect(() => (0, digest_1.candidateDigest)(undefined)).toThrow(/digest input is required/);
78
+ expect(() => (0, digest_1.candidateDigest)({ file: '', cwd })).toThrow(/bounded path/);
79
+ expect(() => (0, digest_1.candidateDigest)({ file: 'policy.json', cwd: '' })).toThrow(/bounded path/);
80
+ fs_1.default.writeFileSync(path_1.default.join(cwd, 'array.json'), '[]');
81
+ expect(() => (0, digest_1.candidateDigest)({ file: 'array.json', cwd })).toThrow(/must be a JSON object/);
82
+ });
83
+ // The point of the command: the digest it discloses is the one approval
84
+ // demands. A digest that merely looked plausible would be worthless, so the
85
+ // proof is that approval ACCEPTS it, not that two functions agree.
86
+ it('produces a digest that the policy approval path accepts end to end', () => {
87
+ const file = write('policy.json', policy());
88
+ const observed = [];
89
+ const boundary = {
90
+ withProjectLease: (_root, operation) => operation(),
91
+ readRegularFile: (target) => { const entry = { bytes: fs_1.default.readFileSync(target), identity: Object.freeze({}) }; observed.push(entry); return entry; },
92
+ writeProjectTransaction: (root, destination, content) => { const target = path_1.default.join(root, destination); fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true }); fs_1.default.writeFileSync(target, content); },
93
+ };
94
+ const approved = (0, store_1.approvePolicyWithBoundary)({ file, scope: 'project', cwd, expectedDigest: (0, digest_1.candidateDigest)({ file, cwd }).digest }, boundary);
95
+ expect(approved.contentDigest).toBe((0, digest_1.candidateDigest)({ file, cwd }).digest);
96
+ expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(cwd, '.awm', 'model-policy.json'), 'utf8')).contentDigest).toBe(approved.contentDigest);
97
+ });
98
+ it('produces a digest that the capability approval path accepts end to end', () => {
99
+ const value = receipt();
100
+ const file = write('receipt.json', value);
101
+ const approved = (0, capabilities_1.approveCapabilities)({ file, cwd, expectedDigest: (0, digest_1.candidateDigest)({ file, cwd }).digest, now: new Date('2026-09-17T12:00:00.000Z') });
102
+ expect(approved).toEqual(value);
103
+ });
104
+ // Falsifiability: the same approval must REJECT any digest but the disclosed
105
+ // one, or the assertion above would pass against a command that emitted noise.
106
+ it('is the only digest approval accepts', () => {
107
+ const value = receipt();
108
+ const file = write('receipt.json', value);
109
+ expect(() => (0, capabilities_1.approveCapabilities)({ file, cwd, expectedDigest: 'f'.repeat(64), now: new Date('2026-09-17T12:00:00.000Z') })).toThrow(/does not match expectedDigest/);
110
+ });
111
+ it('writes nothing: digesting is not approving', () => {
112
+ const file = write('policy.json', policy());
113
+ (0, digest_1.candidateDigest)({ file, cwd });
114
+ expect(fs_1.default.existsSync(path_1.default.join(cwd, '.awm'))).toBe(false);
115
+ expect(fs_1.default.readdirSync(cwd).sort()).toEqual(['policy.json']);
116
+ });
117
+ });
@@ -98,6 +98,80 @@ describe('resolveSelection', () => {
98
98
  value.capabilities.capabilities.effortOverride = 'unsupported';
99
99
  expect((0, resolve_2.resolveV1Dispatch)({ ...value, plan: verifiedV1Plan, optInV1: true })).toMatchObject({ state: 'resolved', outcome: 'native', selection: runtimeDefaultSelection('full') });
100
100
  });
101
+ // Regression for the collapse that made routed model and surrendered effort
102
+ // indistinguishable: degrading effort used to rewrite the selection to
103
+ // fullCapability, so a mechanical slice silently ran the judgment model while
104
+ // the only evidence emitted named effortOverride.
105
+ it('keeps the routed per-profile model when effort override degrades', () => {
106
+ const value = input();
107
+ value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
108
+ value.capabilities.capabilities.effortOverride = 'unsupported';
109
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
110
+ value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('integration'), runtimeDefaultSelection('full')];
111
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('mechanical'), effectiveProfile: 'mechanical', unavailableEvidence: ['effortOverride'] });
112
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'integration' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('integration'), effectiveProfile: 'integration', unavailableEvidence: ['effortOverride'] });
113
+ });
114
+ // An honest receipt for a runtime with no effort control attests ONLY
115
+ // runtime-default selections — it cannot claim `mechanical at low effort` is
116
+ // selectable. Checking availability before degradation rejected exactly that
117
+ // receipt, so the contract only worked for a receipt that overclaimed.
118
+ it('degrades effort against a receipt that attests only runtime-default selections', () => {
119
+ const value = input();
120
+ value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
121
+ value.capabilities.capabilities.effortOverride = 'unsupported';
122
+ value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('integration'), runtimeDefaultSelection('judgment'), runtimeDefaultSelection('full')];
123
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
124
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'degraded', selection: runtimeDefaultSelection('mechanical'), unavailableEvidence: ['effortOverride'] });
125
+ });
126
+ // The runtime default is one selection; a per-profile selection at
127
+ // runtime-default effort is a different one. Requiring them equal made every
128
+ // profile but the default model unroutable, with modelOverride attested
129
+ // supported and the selection itself attested available.
130
+ it('routes a per-profile runtime-default selection whose model differs from the runtime default', () => {
131
+ const value = input();
132
+ value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
133
+ value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
134
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
135
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'resolved', outcome: 'native', selection: runtimeDefaultSelection('mechanical'), effectiveProfile: 'mechanical' });
136
+ });
137
+ it('still blocks a runtime-default selection the receipt does not attest at all', () => {
138
+ const value = input();
139
+ value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
140
+ value.capabilities.availableSelections = [runtimeDefaultSelection('full')];
141
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
142
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNAVAILABLE' }] });
143
+ });
144
+ it('still blocks a runtime-default selection when the receipt declares no runtime default', () => {
145
+ const value = input();
146
+ value.policy.content.mappings[0].profiles.mechanical = runtimeDefaultSelection('mechanical');
147
+ value.capabilities.availableSelections = [runtimeDefaultSelection('mechanical')];
148
+ delete value.capabilities.runtimeDefaultSelection;
149
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_SELECTION_UNAVAILABLE' }] });
150
+ });
151
+ it('blocks effort degradation when the routed model at runtime-default effort is not attested', () => {
152
+ const value = input();
153
+ value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
154
+ value.capabilities.capabilities.effortOverride = 'unsupported';
155
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
156
+ value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('full')];
157
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE' }] });
158
+ });
159
+ it('keeps effort strict when the mapping does not allow degrading it', () => {
160
+ const value = input();
161
+ value.capabilities.capabilities.effortOverride = 'unsupported';
162
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
163
+ value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
164
+ expect(value.policy.content.mappings[0].degradation.allowMissingEffortOverride).toBe(false);
165
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE' }] });
166
+ });
167
+ it('never degrades effort on an unverified capability, only on an attested unsupported one', () => {
168
+ const value = input();
169
+ value.policy.content.mappings[0].degradation.allowMissingEffortOverride = true;
170
+ value.capabilities.capabilities.effortOverride = 'unverified';
171
+ value.capabilities.runtimeDefaultSelection = runtimeDefaultSelection('full');
172
+ value.capabilities.availableSelections = [...value.capabilities.availableSelections, runtimeDefaultSelection('mechanical'), runtimeDefaultSelection('full')];
173
+ expect((0, resolve_1.resolveSelection)({ ...value, requestedProfile: 'mechanical' })).toMatchObject({ state: 'blocked', diagnostics: [{ code: 'ROUTING_EFFORT_OVERRIDE_UNAVAILABLE', message: expect.stringMatching(/unverified/) }] });
174
+ });
101
175
  it('does not require effort override after model fallback selects an attested runtime-default full selection', () => {
102
176
  const value = input();
103
177
  value.policy.content.mappings[0].fullCapability = runtimeDefaultSelection('full');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.9.0",
3
+ "version": "9.10.1",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file