agentic-workflow-manager 9.7.1 → 9.9.0

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 (107) hide show
  1. package/README.md +5 -1
  2. package/dist/src/commands/job/exec-wrapper.js +53 -3
  3. package/dist/src/commands/job/gate.js +18 -0
  4. package/dist/src/commands/job/index.js +61 -0
  5. package/dist/src/commands/model-policy/index.js +73 -0
  6. package/dist/src/commands/plan/index.js +182 -7
  7. package/dist/src/commands/sensors/bootstrap.js +36 -5
  8. package/dist/src/commands/sensors/compatibility/contract.js +19 -4
  9. package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
  10. package/dist/src/commands/sensors/compatibility/live.js +18 -1
  11. package/dist/src/commands/sensors/compatibility/probe.js +8 -2
  12. package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
  13. package/dist/src/commands/sensors/coverage/evidence.js +19 -10
  14. package/dist/src/commands/sensors/coverage/index.js +11 -2
  15. package/dist/src/commands/sensors/coverage/resolve.js +10 -0
  16. package/dist/src/commands/sensors/exec.js +16 -1
  17. package/dist/src/commands/sensors/migrate.js +62 -2
  18. package/dist/src/commands/sensors/prepare.js +2 -0
  19. package/dist/src/commands/sensors/result.js +2 -0
  20. package/dist/src/commands/sensors/run.js +64 -1
  21. package/dist/src/commands/sensors/status.js +6 -4
  22. package/dist/src/commands/watch/apply.js +32 -0
  23. package/dist/src/commands/watch/archive-unused.js +138 -0
  24. package/dist/src/commands/watch/generations.js +32 -9
  25. package/dist/src/commands/watch/index.js +108 -2
  26. package/dist/src/commands/watch/init.js +149 -18
  27. package/dist/src/commands/watch/supervisor.js +167 -13
  28. package/dist/src/commands/watch/teardown-driver.js +13 -13
  29. package/dist/src/commands/watch/track-state.js +59 -0
  30. package/dist/src/commands/watch/tracks.js +38 -102
  31. package/dist/src/core/admission/index.js +231 -0
  32. package/dist/src/core/admission/registry-contracts.js +290 -0
  33. package/dist/src/core/journal/fingerprint.js +35 -1
  34. package/dist/src/core/journal/paths.js +11 -0
  35. package/dist/src/core/journal/requests.js +1 -1
  36. package/dist/src/core/journal/store.js +146 -4
  37. package/dist/src/core/journal/types.js +57 -2
  38. package/dist/src/core/migration/historical-provenance.js +18 -0
  39. package/dist/src/core/migration/index.js +322 -0
  40. package/dist/src/core/model-policy/canonical.js +18 -0
  41. package/dist/src/core/model-policy/capabilities.js +197 -0
  42. package/dist/src/core/model-policy/capability-types.js +2 -0
  43. package/dist/src/core/model-policy/journal.js +124 -0
  44. package/dist/src/core/model-policy/paths.js +12 -0
  45. package/dist/src/core/model-policy/resolve.js +116 -0
  46. package/dist/src/core/model-policy/store.js +182 -0
  47. package/dist/src/core/model-policy/types.js +2 -0
  48. package/dist/src/core/model-policy/validate.js +112 -0
  49. package/dist/src/core/plan/identity.js +77 -0
  50. package/dist/src/core/plan/validate.js +191 -78
  51. package/dist/src/index.js +2 -0
  52. package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
  53. package/dist/tests/commands/job/gate-reconcile.test.js +40 -10
  54. package/dist/tests/commands/job/gate.test.js +9 -0
  55. package/dist/tests/commands/job/routing-report-cli.test.js +46 -0
  56. package/dist/tests/commands/job/routing.test.js +11 -0
  57. package/dist/tests/commands/model-policy/index.test.js +57 -0
  58. package/dist/tests/commands/pin.test.js +23 -23
  59. package/dist/tests/commands/plan/index.test.js +682 -14
  60. package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
  61. package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
  62. package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
  63. package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
  64. package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
  65. package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
  66. package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
  67. package/dist/tests/commands/sensors/exec.test.js +112 -0
  68. package/dist/tests/commands/sensors/run-changed.test.js +11 -0
  69. package/dist/tests/commands/sensors/run.test.js +15 -2
  70. package/dist/tests/commands/sensors/status.test.js +3 -3
  71. package/dist/tests/commands/watch/apply.test.js +12 -0
  72. package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
  73. package/dist/tests/commands/watch/generations.test.js +113 -4
  74. package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
  75. package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
  76. package/dist/tests/commands/watch/track-finalize.test.js +20 -3
  77. package/dist/tests/commands/watch/track-freeze.test.js +43 -30
  78. package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
  79. package/dist/tests/commands/watch/watch-init.test.js +244 -0
  80. package/dist/tests/core/admission/index.test.js +140 -0
  81. package/dist/tests/core/journal/fingerprint.test.js +22 -0
  82. package/dist/tests/core/journal/store.test.js +53 -0
  83. package/dist/tests/core/journal/types.test.js +7 -0
  84. package/dist/tests/core/migration/facts.test.js +289 -0
  85. package/dist/tests/core/migration/historical-provenance.test.js +25 -0
  86. package/dist/tests/core/model-policy/capabilities.test.js +365 -0
  87. package/dist/tests/core/model-policy/journal.test.js +46 -0
  88. package/dist/tests/core/model-policy/policy.test.js +47 -0
  89. package/dist/tests/core/model-policy/resolve.test.js +111 -0
  90. package/dist/tests/core/model-policy/store.test.js +177 -0
  91. package/dist/tests/core/plan/validate.test.js +360 -8
  92. package/dist/tests/core/secure-fs/native-bridge.test.js +81 -4
  93. package/dist/tests/integration/pack-e2e.test.js +4 -1
  94. package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
  95. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
  96. package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
  97. package/dist/tests/providers/injection-config.test.js +14 -6
  98. package/dist/tests/structural/directory-fsync-is-single-source.test.js +97 -0
  99. package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
  100. package/dist/tests/structural/native-release-artifacts.test.js +30 -0
  101. package/package.json +4 -3
  102. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  103. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  104. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  105. package/prebuilds/linux-x64/secure_fs.node +0 -0
  106. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  107. package/prebuilds/win32-x64/secure_fs.node +0 -0
package/README.md CHANGED
@@ -41,9 +41,13 @@ This generates the runnable output under `dist/index.js`.
41
41
  ### 4. Running the Tests
42
42
  Before submitting a PR, ensure that the core discovery/executor functionality passes the existing test suite:
43
43
  ```bash
44
+ npm run native:build
44
45
  npm test
45
46
  ```
46
- The goal is to maintain 100% passing tests for any new CLI parameter flags.
47
+ `native:build` creates the ignored, host-specific `secure-fs` artifact required
48
+ by the integration fixtures; it must be run again after a clean checkout or a
49
+ change under `native/`. The goal is to maintain 100% passing tests for any new
50
+ CLI parameter flags.
47
51
 
48
52
  ### 5. Local Execution (Testing changes locally)
49
53
 
@@ -17,19 +17,33 @@ const fs_1 = __importDefault(require("fs"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const child_process_1 = require("child_process");
19
19
  const process_1 = require("../../core/journal/process");
20
+ const paths_1 = require("../../core/journal/paths");
21
+ const types_1 = require("../../core/journal/types");
22
+ const native_bridge_1 = require("../../core/secure-fs/native-bridge");
20
23
  const fingerprint_1 = require("../../core/journal/fingerprint");
21
24
  const redact_1 = require("../../core/journal/redact");
22
25
  const atomic_file_1 = require("../../core/atomic-file");
26
+ function validateSidecarIdentity(logsRoot, jobId, nonce) {
27
+ if (typeof logsRoot !== 'string' || logsRoot.length === 0 || logsRoot.length > 4096 || /[\u0000-\u001F\u007F]/.test(logsRoot)
28
+ || typeof jobId !== 'string' || !/^[A-Za-z0-9_-][A-Za-z0-9_.-]{0,127}$/.test(jobId)
29
+ || typeof nonce !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(nonce)
30
+ || jobId.length + nonce.length > 230)
31
+ throw new Error('unsafe or unbounded sidecar identity');
32
+ }
23
33
  function claimPath(logsRoot, jobId, nonce) {
34
+ validateSidecarIdentity(logsRoot, jobId, nonce);
24
35
  return path_1.default.join(logsRoot, `${jobId}.${nonce}.claim`);
25
36
  }
26
37
  function identityPath(logsRoot, jobId, nonce) {
38
+ validateSidecarIdentity(logsRoot, jobId, nonce);
27
39
  return path_1.default.join(logsRoot, `${jobId}.${nonce}.identity.json`);
28
40
  }
29
41
  function resultPath(logsRoot, jobId, nonce) {
42
+ validateSidecarIdentity(logsRoot, jobId, nonce);
30
43
  return path_1.default.join(logsRoot, `${jobId}.${nonce}.result.json`);
31
44
  }
32
45
  function logPath(logsRoot, jobId, nonce) {
46
+ validateSidecarIdentity(logsRoot, jobId, nonce);
33
47
  return path_1.default.join(logsRoot, `${jobId}.${nonce}.log`);
34
48
  }
35
49
  function replayVerdict(logsRoot, jobId, nonce) {
@@ -45,11 +59,46 @@ const MAX_LOG_BYTES = 1024 * 1024; // retencion acotada (R2.5)
45
59
  // dispara, tipicamente entregados en 1 tick del event loop) sin acercarse a
46
60
  // una espera perceptible si un descendiente hereda los fds y nunca cierra.
47
61
  const STDIO_GRACE_MS = 300;
62
+ /** Only a committed generation intent may suppress controller output. A user
63
+ * job with the same name retains normal verifier logs. Never persist model
64
+ * output (which may include prompts/source bodies) in generation custody. */
65
+ function isCommittedController(logsRoot, repoRoot, jobId, nonce, argv) {
66
+ if (!/^controller-gen-[1-9][0-9]*$/.test(jobId))
67
+ return false;
68
+ try {
69
+ const bytes = native_bridge_1.secureFs.readRegularFile(path_1.default.resolve(logsRoot, '..', 'state.json'), 1024 * 1024).bytes;
70
+ const state = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
71
+ if (!(0, types_1.isWellFormedState)(state))
72
+ throw new Error('controller custody journal invalid');
73
+ const actual = fs_1.default.lstatSync(logsRoot, { bigint: true });
74
+ const expected = fs_1.default.lstatSync((0, paths_1.logsDir)(repoRoot, state.branch), { bigint: true });
75
+ if (!actual.isDirectory() || actual.isSymbolicLink() || !expected.isDirectory() || expected.isSymbolicLink()
76
+ || actual.ino === 0n || actual.dev !== expected.dev || actual.ino !== expected.ino)
77
+ throw new Error('controller custody directory identity invalid');
78
+ const committed = state.generations.some(gen => gen.controllerJobId === jobId && jobId === `controller-gen-${gen.n}`
79
+ && gen.spawnNonce === nonce && gen.resumePrompt === undefined && (0, types_1.isControllerRecoveryAction)(gen.resumeAction)
80
+ && gen.launchArgvDigest === (0, process_1.argvDigest)(argv));
81
+ if (committed)
82
+ return true;
83
+ const ordinary = state.jobs[jobId];
84
+ if (ordinary?.spawnNonce === nonce && (0, process_1.argvDigest)(ordinary.argv) === (0, process_1.argvDigest)(argv))
85
+ return false;
86
+ throw new Error('controller custody launch identity unproven');
87
+ }
88
+ catch {
89
+ throw new Error('controller custody identity unavailable or unsafe: refusing launch before claim');
90
+ }
91
+ }
48
92
  async function runExecWrapper(opts) {
49
93
  const { logsRoot, jobId, nonce, argv, cwd } = opts;
94
+ validateSidecarIdentity(logsRoot, jobId, nonce);
50
95
  const repoRoot = opts.repoRoot ?? process.cwd();
51
- if (argv.length === 0)
52
- throw new Error('argv vacio');
96
+ if (!Array.isArray(argv) || argv.length === 0 || argv.length > 256
97
+ || argv.some(argument => typeof argument !== 'string' || argument.length > 65536 || argument.includes('\0'))
98
+ || argv[0].length === 0 || argv.reduce((bytes, argument) => bytes + Buffer.byteLength(argument, 'utf8'), 0) > 1024 * 1024)
99
+ throw new Error('argv invalid or unbounded');
100
+ const safeCwd = (0, fingerprint_1.resolveWorkingDirectory)(repoRoot, cwd).absolute;
101
+ const omitControllerOutput = isCommittedController(logsRoot, repoRoot, jobId, nonce, argv);
53
102
  fs_1.default.mkdirSync(logsRoot, { recursive: true, mode: 0o700 });
54
103
  // (1) claim exclusivo DURABLE — wx + fsync de archivo y de directorio
55
104
  let fd;
@@ -77,7 +126,6 @@ async function runExecWrapper(opts) {
77
126
  // process group por job, independiente del supervisor (R4.7: shell:false,
78
127
  // argv como array, secretos solo por referencia de entorno).
79
128
  const [exe, ...args] = argv;
80
- const safeCwd = (0, fingerprint_1.resolveWorkingDirectory)(repoRoot, cwd).absolute;
81
129
  const child = (0, child_process_1.spawn)(exe, args, {
82
130
  cwd: safeCwd, shell: false, detached: true,
83
131
  env: { ...process.env, [process_1.NONCE_ENV]: nonce },
@@ -105,6 +153,8 @@ async function runExecWrapper(opts) {
105
153
  const outputChunks = [];
106
154
  const logFile = logPath(logsRoot, jobId, nonce);
107
155
  const capture = (chunk) => {
156
+ if (omitControllerOutput)
157
+ return;
108
158
  if (captured >= MAX_LOG_BYTES)
109
159
  return;
110
160
  const accepted = chunk.subarray(0, MAX_LOG_BYTES - captured);
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.computeGate = computeGate;
4
4
  exports.computeTrackGate = computeTrackGate;
5
5
  const LIVE = ['received', 'spawn-intent', 'claimed', 'running', 'cancel-requested'];
6
+ function sameSelection(left, right) {
7
+ return left.selector.kind === right.selector.kind && left.selector.id === right.selector.id && left.effort.kind === right.effort.kind && left.effort.value === right.effort.value;
8
+ }
6
9
  function evaluateEvidence(state, fingerprintNow, scope) {
7
10
  const reasons = [];
8
11
  if (state.cycle.status === 'BLOCKED') {
@@ -123,6 +126,21 @@ function evaluateEvidence(state, fingerprintNow, scope) {
123
126
  }
124
127
  }
125
128
  for (const v of state.verdicts) {
129
+ if (v.routingAttemptId !== undefined) {
130
+ const attempt = state.routingAttempts?.find((candidate) => candidate.id === v.routingAttemptId);
131
+ const binding = state.planBinding;
132
+ const bound = binding !== undefined && binding.digest === attempt?.envelope.planDigest && binding.executionDigest === attempt?.envelope.executionDigest;
133
+ const observed = attempt?.observed;
134
+ const selectionMatches = attempt !== undefined && observed !== undefined && sameSelection(observed, attempt.envelope.resolved);
135
+ // A reported actual selection is authoritative: degradation may
136
+ // explain absent observability, never excuse an observed mismatch.
137
+ const nativeObservationValid = observed !== undefined
138
+ ? selectionMatches
139
+ : attempt?.envelope.outcome === 'degraded' && (attempt.envelope.unavailableEvidence.length ?? 0) > 0 && Boolean(attempt.reasonCode);
140
+ if (attempt === undefined || !['active', 'complete'].includes(attempt.state) || !attempt.nativeAgentId || attempt.obligationId !== v.obligationId || attempt.fingerprint !== v.fingerprint || !bound || !nativeObservationValid) {
141
+ reasons.push({ category: 'routing-evidence', detail: `verdict routed ${v.id} no tiene evidencia de intento observado, vigente y ligado` });
142
+ }
143
+ }
126
144
  if (v.result !== 'pass') {
127
145
  const fix = state.fixes.find((f) => f.verdictId === v.id);
128
146
  if (fix === undefined || !fix.closed)
@@ -23,6 +23,9 @@ const paths_1 = require("../../core/journal/paths");
23
23
  const lock_1 = require("../watch/lock");
24
24
  const atomic_file_1 = require("../../core/atomic-file");
25
25
  const context_1 = require("../../core/tracks/context");
26
+ const journal_1 = require("../../core/model-policy/journal");
27
+ const types_1 = require("../../core/journal/types");
28
+ const json_1 = require("../../core/plan/json");
26
29
  const fs_1 = __importDefault(require("fs"));
27
30
  function branchOf(cwd) {
28
31
  // stdio explicito (ver EXEC_STDIO en journal/process.ts): evita el relay
@@ -43,6 +46,19 @@ function realFingerprintNow(repo) {
43
46
  }
44
47
  };
45
48
  }
49
+ function readBoundedJson(file) {
50
+ if (typeof file !== 'string' || file.length === 0 || file.length > 4096)
51
+ throw new Error('routing file path is invalid');
52
+ const stat = fs_1.default.lstatSync(file);
53
+ if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 256 * 1024)
54
+ throw new Error('routing file must be a bounded non-symlink regular file');
55
+ try {
56
+ return (0, json_1.parseJsonNoDuplicate)(fs_1.default.readFileSync(file, 'utf8'));
57
+ }
58
+ catch {
59
+ throw new Error('routing file must contain JSON without duplicate keys');
60
+ }
61
+ }
46
62
  /** Guard de entrada (R9.4): sin descriptor de track, es un no-op — el caso
47
63
  * comun de siempre no paga costo ni riesgo nuevo. Con descriptor presente
48
64
  * que no autentica (fencing/realpath/journalId no coinciden), rechaza ANTES
@@ -143,6 +159,36 @@ function registerJobCommand(program) {
143
159
  assertAuthenticatedCwd(repo, branch);
144
160
  (0, heartbeat_1.emitHeartbeat)(repo, branch, opts.generation);
145
161
  });
162
+ job.command('routing-reserve')
163
+ .requiredOption('--generation <token>').requiredOption('--obligation <id>').requiredOption('--lineage <id>')
164
+ .requiredOption('--envelope-file <file>').requiredOption('--fingerprint <sha>').option('--cwd <root>', 'repository root', '.').option('--json')
165
+ .action((opts) => {
166
+ const envelope = readBoundedJson(opts.envelopeFile);
167
+ if (!(0, types_1.isRoutingEnvelope)(envelope) || !/^[a-f0-9]{64}$/.test(opts.fingerprint))
168
+ throw new Error('routing-reserve requires a valid envelope and fingerprint');
169
+ const repo = path_1.default.resolve(opts.cwd);
170
+ const branch = branchOf(repo);
171
+ assertAuthenticatedCwd(repo, branch);
172
+ const emitted = (0, requests_1.emitRequest)(repo, branch, { kind: 'routing-reserve', generationToken: opts.generation, idempotencyKey: crypto_1.default.createHash('sha256').update(`routing-reserve:${opts.generation}:${opts.obligation}:${opts.lineage}:${opts.fingerprint}:${JSON.stringify(envelope)}`).digest('hex'), payload: { obligationId: opts.obligation, lineageId: opts.lineage, envelope, fingerprint: opts.fingerprint } });
173
+ process.stdout.write(JSON.stringify({ requestId: emitted.requestId }) + '\n');
174
+ });
175
+ job.command('routing-observe')
176
+ .requiredOption('--generation <token>').requiredOption('--attempt <id>').requiredOption('--native-agent-id <id>')
177
+ .requiredOption('--observation-file <file>').option('--cwd <root>', 'repository root', '.').option('--json')
178
+ .action((opts) => {
179
+ const observation = readBoundedJson(opts.observationFile);
180
+ if (typeof observation !== 'object' || observation === null || Array.isArray(observation))
181
+ throw new Error('routing-observe requires an observation object');
182
+ const observed = observation.observed;
183
+ const unavailableReason = observation.unavailableReason;
184
+ if ((observed !== undefined && !(0, types_1.isRoutingSelection)(observed)) || (unavailableReason !== undefined && typeof unavailableReason !== 'string'))
185
+ throw new Error('routing-observe requires a valid observation');
186
+ const repo = path_1.default.resolve(opts.cwd);
187
+ const branch = branchOf(repo);
188
+ assertAuthenticatedCwd(repo, branch);
189
+ const emitted = (0, requests_1.emitRequest)(repo, branch, { kind: 'routing-observe', generationToken: opts.generation, idempotencyKey: crypto_1.default.createHash('sha256').update(`routing-observe:${opts.generation}:${opts.attempt}:${opts.nativeAgentId}:${JSON.stringify(observation)}`).digest('hex'), payload: { attemptId: opts.attempt, nativeAgentId: opts.nativeAgentId, ...(observed === undefined ? {} : { observed }), ...(unavailableReason === undefined ? {} : { unavailableReason }) } });
190
+ process.stdout.write(JSON.stringify({ requestId: emitted.requestId }) + '\n');
191
+ });
146
192
  job.command('ps').action(() => {
147
193
  const repo = process.cwd();
148
194
  const branch = branchOf(repo);
@@ -216,6 +262,21 @@ function registerJobCommand(program) {
216
262
  if (!g.pass)
217
263
  process.exit(1); // falla cerrado (R3.2)
218
264
  });
265
+ job.command('routing-report')
266
+ .description('informe de routing read-only; nunca expone envelopes ni identidad nativa')
267
+ .option('--json', 'emit the routing report as JSON')
268
+ .action(() => {
269
+ const repo = process.cwd();
270
+ const branch = branchOf(repo);
271
+ assertAuthenticatedCwd(repo, branch);
272
+ const r = (0, store_1.readJournal)(repo, branch);
273
+ if (r.corrupt || r.state === null) {
274
+ process.stdout.write(JSON.stringify({ corruptState: true }) + '\n');
275
+ process.exit(1);
276
+ return;
277
+ }
278
+ process.stdout.write(JSON.stringify((0, journal_1.routingReport)(r.state), null, 2) + '\n');
279
+ });
219
280
  job.command('reap')
220
281
  .description('lista procesos de jobs (identidad completa); --execute --jobs <ids...> para terminar confirmando')
221
282
  .option('--execute', 'ejecutar la terminacion de los jobs listados en --jobs')
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerModelPolicyCommand = registerModelPolicyCommand;
4
+ const crypto_1 = require("crypto");
5
+ const providers_1 = require("../../providers");
6
+ const store_1 = require("../../core/model-policy/store");
7
+ const validate_1 = require("../../core/model-policy/validate");
8
+ const capabilities_1 = require("../../core/model-policy/capabilities");
9
+ const roles = ['implementer', 'specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'];
10
+ const implementerProfiles = ['mechanical', 'integration', 'judgment'];
11
+ 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 } };
12
+ const protocolDigest = (0, crypto_1.createHash)('sha256').update(JSON.stringify(contract), 'utf8').digest('hex');
13
+ function text(value, label) { if (typeof value !== 'string' || value.length === 0 || value.startsWith('--') || value.length > 4096 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
14
+ throw new Error(`${label} requires a non-empty value without control characters`); }
15
+ function output(value, json) { process.stdout.write(json ? `${JSON.stringify(value)}\n` : `${JSON.stringify(value, null, 2)}\n`); }
16
+ function registerModelPolicyCommand(program, deps = {}) {
17
+ if (!program || typeof program.command !== 'function')
18
+ throw new Error('program must be a Commander command');
19
+ const approve = deps.approvePolicy ?? store_1.approvePolicy;
20
+ const read = deps.readEffectivePolicy ?? store_1.readEffectivePolicy;
21
+ const approveReceipt = deps.approveCapabilities ?? capabilities_1.approveCapabilities;
22
+ const readReceipt = deps.readCapabilities ?? capabilities_1.readCapabilities;
23
+ const command = program.command('model-policy').description('inspect and explicitly approve portable routing policy');
24
+ command.command('contract').option('--cwd <path>').option('--json').action((options) => { if (options.cwd !== undefined)
25
+ text(options.cwd, '--cwd'); output({ ...contract, protocolDigest }, options.json === true); });
26
+ 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
+ text(options.file, '--file');
28
+ text(options.scope, '--scope');
29
+ text(options.expectedDigest, '--expected-digest');
30
+ if (options.cwd !== undefined)
31
+ text(options.cwd, '--cwd');
32
+ if (options.replaceDigest !== undefined)
33
+ text(options.replaceDigest, '--replace-digest');
34
+ (0, validate_1.assertDigest)(options.expectedDigest, 'expectedDigest');
35
+ if (options.replaceDigest !== undefined)
36
+ (0, validate_1.assertDigest)(options.replaceDigest, 'replaceDigest');
37
+ const policy = approve({ file: options.file, scope: options.scope, cwd: options.cwd ?? process.cwd(), expectedDigest: options.expectedDigest, replaceDigest: options.replaceDigest });
38
+ output({ state: 'approved', policy }, options.json === true);
39
+ });
40
+ command.command('capabilities approve').requiredOption('--file <path>').requiredOption('--expected-digest <sha>').option('--replace-digest <sha>').option('--cwd <path>').option('--json').action((options) => {
41
+ text(options.file, '--file');
42
+ text(options.expectedDigest, '--expected-digest');
43
+ if (options.replaceDigest !== undefined)
44
+ text(options.replaceDigest, '--replace-digest');
45
+ if (options.cwd !== undefined)
46
+ text(options.cwd, '--cwd');
47
+ (0, validate_1.assertDigest)(options.expectedDigest, 'expectedDigest');
48
+ if (options.replaceDigest)
49
+ (0, validate_1.assertDigest)(options.replaceDigest, 'replaceDigest');
50
+ output({ state: 'approved', receipt: approveReceipt({ file: options.file, cwd: options.cwd ?? process.cwd(), expectedDigest: options.expectedDigest, replaceDigest: options.replaceDigest }) }, options.json === true);
51
+ });
52
+ command.command('status').requiredOption('--provider <target>').requiredOption('--runtime-kind <kind>').requiredOption('--runtime-version <version>').requiredOption('--account-scope-digest <sha>').option('--cwd <path>').option('--json').action((options) => {
53
+ text(options.provider, '--provider');
54
+ text(options.runtimeKind, '--runtime-kind');
55
+ text(options.runtimeVersion, '--runtime-version');
56
+ text(options.accountScopeDigest, '--account-scope-digest');
57
+ if (options.cwd !== undefined)
58
+ text(options.cwd, '--cwd');
59
+ if (!providers_1.AGENT_TARGETS.includes(options.provider))
60
+ throw new Error('--provider is invalid');
61
+ if (!/^[a-z0-9][a-z0-9_-]{0,127}$/.test(options.runtimeKind))
62
+ throw new Error('--runtime-kind is invalid');
63
+ if (!/^\d+\.\d+\.\d+$/.test(options.runtimeVersion))
64
+ throw new Error('--runtime-version must be numeric semver');
65
+ (0, validate_1.assertDigest)(options.accountScopeDigest, 'accountScopeDigest');
66
+ const cwd = options.cwd ?? process.cwd();
67
+ const effective = read(cwd);
68
+ const capability = readReceipt({ target: options.provider, kind: options.runtimeKind, version: options.runtimeVersion, accountScopeDigest: options.accountScopeDigest }, new Date());
69
+ output({ policy: effective, capability }, options.json === true);
70
+ if (effective.state !== 'approved' || capability.state !== 'current')
71
+ process.exitCode = 2;
72
+ });
73
+ }
@@ -1,13 +1,43 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.exitCodeFor = exitCodeFor;
4
7
  exports.formatReport = formatReport;
5
8
  exports.registerPlanCommand = registerPlanCommand;
6
9
  const validate_1 = require("../../core/plan/validate");
7
- const SUPPORTED_SCHEMA = 'compact-slices/v1';
10
+ const admission_1 = require("../../core/admission");
11
+ const check_1 = require("../../core/currentness/check");
12
+ const run_1 = require("../sensors/run");
13
+ const config_1 = require("../../utils/config");
14
+ const providers_1 = require("../../providers");
15
+ const registries_1 = require("../../core/registries");
16
+ const registry_contracts_1 = require("../../core/admission/registry-contracts");
17
+ const path_1 = __importDefault(require("path"));
18
+ const child_process_1 = require("child_process");
19
+ const store_1 = require("../../core/journal/store");
20
+ const migration_1 = require("../../core/migration");
21
+ const store_2 = require("../../core/model-policy/store");
22
+ const capabilities_1 = require("../../core/model-policy/capabilities");
23
+ const resolve_1 = require("../../core/model-policy/resolve");
24
+ const journal_1 = require("../../core/model-policy/journal");
25
+ const SUPPORTED_SCHEMA = 'compact-slices/v1, compact-slices/v2';
8
26
  const MAX_PATH_LENGTH = 4096;
9
27
  const MAX_DIAGNOSTICS = 20;
10
28
  const MAX_DIAGNOSTIC_LENGTH = 4096;
29
+ function journalObservation(cwd) {
30
+ try {
31
+ const branch = (0, child_process_1.execFileSync)('git', ['branch', '--show-current'], { cwd, encoding: 'utf8', stdio: 'pipe' }).trim();
32
+ if (!branch)
33
+ return { journalState: null, journalCorrupt: true };
34
+ const journal = (0, store_1.readJournal)(cwd, branch);
35
+ return { journalState: journal.state, journalCorrupt: journal.corrupt };
36
+ }
37
+ catch {
38
+ return { journalState: null, journalCorrupt: true };
39
+ }
40
+ }
11
41
  function assertText(value, name) {
12
42
  if (typeof value !== 'string' || value.length === 0 || value.length > MAX_PATH_LENGTH || value.startsWith('--') || /[\u0000-\u001F\u007F-\u009F]/.test(value)) {
13
43
  throw new Error(`${name} must be a non-empty path without control characters`);
@@ -19,6 +49,15 @@ function terminalSafe(value) {
19
49
  function assertDependencies(deps) {
20
50
  if (!deps || typeof deps.validatePlanFile !== 'function')
21
51
  throw new Error('validatePlanFile must be a function');
52
+ if (deps.admitPlan !== undefined && typeof deps.admitPlan !== 'function')
53
+ throw new Error('admitPlan must be a function');
54
+ }
55
+ function admissionOutput(report, json) {
56
+ report = (0, admission_1.sanitizeAdmissionReport)(report);
57
+ if (json)
58
+ return `${JSON.stringify(report)}\n`;
59
+ const diagnostic = report.diagnostics[0];
60
+ return `Plan admission: ${report.state}${report.provider ? ` (${terminalSafe(report.provider)})` : ''}${diagnostic ? `\n- ${terminalSafe(diagnostic.code)}: ${terminalSafe(diagnostic.message)}` : ''}\n`;
22
61
  }
23
62
  function boundedDiagnostics(diagnostics) {
24
63
  if (!Array.isArray(diagnostics))
@@ -34,16 +73,42 @@ function boundedDiagnostics(diagnostics) {
34
73
  };
35
74
  });
36
75
  }
76
+ function assertReport(report) {
77
+ if (!report || typeof report !== 'object' || Array.isArray(report))
78
+ throw new Error('plan validator returned an invalid report');
79
+ switch (report.state) {
80
+ case 'valid':
81
+ (0, validate_1.assertVerifiedValidPlanReport)(report);
82
+ return;
83
+ case 'migration-required':
84
+ if (report.reason !== 'unmarked-plan')
85
+ throw new Error('plan validator returned an invalid migration reason');
86
+ return;
87
+ case 'invalid':
88
+ boundedDiagnostics(report.diagnostics);
89
+ return;
90
+ case 'unsupported':
91
+ if (typeof report.schema !== 'string' || report.schema.length === 0 || report.schema.length > MAX_PATH_LENGTH) {
92
+ throw new Error('plan validator returned an invalid unsupported schema');
93
+ }
94
+ boundedDiagnostics(report.diagnostics);
95
+ return;
96
+ default:
97
+ throw new Error('plan validator returned an unknown report state');
98
+ }
99
+ }
37
100
  function reportPayload(report, planPath) {
101
+ assertReport(report);
38
102
  switch (report.state) {
39
103
  case 'valid':
40
104
  return {
41
105
  state: report.state, path: planPath, schema: report.schema, planId: report.manifest.planId,
106
+ planDigest: report.planDigest,
42
107
  requirements: report.manifest.requirements.length, sources: report.manifest.sources.length,
43
108
  commands: report.manifest.commands.length, slices: report.manifest.slices.length, completeOwnership: true,
44
109
  };
45
- case 'legacy':
46
- return { state: report.state, path: planPath, message: 'existing full-quality path applies; compact optimization was not requested.' };
110
+ case 'migration-required':
111
+ return { state: report.state, path: planPath, reason: report.reason };
47
112
  case 'invalid':
48
113
  return { state: report.state, path: planPath, diagnostics: boundedDiagnostics(report.diagnostics) };
49
114
  case 'unsupported':
@@ -56,15 +121,16 @@ function reportPayload(report, planPath) {
56
121
  }
57
122
  }
58
123
  function exitCodeFor(report) {
59
- return report.state === 'valid' || report.state === 'legacy' ? 0 : 2;
124
+ assertReport(report);
125
+ return report.state === 'valid' ? 0 : 2;
60
126
  }
61
127
  function formatReport(report, planPath) {
62
128
  const payload = reportPayload(report, planPath);
63
129
  switch (report.state) {
64
130
  case 'valid':
65
131
  return `Plan validation: valid "${terminalSafe(planPath)}" (${terminalSafe(report.schema)}; ${payload.slices} slices; ${payload.requirements} requirements; complete ownership)\n`;
66
- case 'legacy':
67
- return `Plan validation: legacy "${terminalSafe(planPath)}" — existing full-quality path applies; compact optimization was not requested.\n`;
132
+ case 'migration-required':
133
+ return `Plan validation: migration-required "${terminalSafe(planPath)}" (${terminalSafe(report.reason)})\nMigrate this plan to compact-slices/v1 before execution.\n`;
68
134
  case 'invalid':
69
135
  return `Plan validation: invalid "${terminalSafe(planPath)}"\n${payload.diagnostics.map(diagnostic => `- ${terminalSafe(diagnostic.code)}: ${terminalSafe(diagnostic.message)}${diagnostic.field ? ` (${terminalSafe(diagnostic.field)})` : ''}`).join('\n')}\n`;
70
136
  case 'unsupported':
@@ -73,11 +139,72 @@ function formatReport(report, planPath) {
73
139
  throw new Error('plan validator returned an unknown report state');
74
140
  }
75
141
  }
76
- function registerPlanCommand(program, deps = { validatePlanFile: validate_1.validatePlanFile }) {
142
+ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.validatePlanFile, admitPlan: admission_1.admitPlan }) {
77
143
  if (!program || typeof program.command !== 'function')
78
144
  throw new Error('program must be a Commander command');
79
145
  assertDependencies(deps);
80
146
  const plan = program.command('plan').description('inspect plan contracts');
147
+ 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) => {
148
+ assertText(planPath, 'plan path');
149
+ for (const [value, label] of [[options.provider, '--provider'], [options.runtimeKind, '--runtime-kind'], [options.runtimeVersion, '--runtime-version'], [options.accountScopeDigest, '--account-scope-digest'], [options.role, '--role']])
150
+ assertText(value, label);
151
+ if (options.slice)
152
+ assertText(options.slice, '--slice');
153
+ if (options.lineage)
154
+ assertText(options.lineage, '--lineage');
155
+ const cwd = options.cwd ?? process.cwd();
156
+ assertText(cwd, '--cwd');
157
+ const report = deps.validatePlanFile(planPath, cwd);
158
+ assertReport(report);
159
+ if (report.state !== 'valid') {
160
+ process.stdout.write(options.json ? `${JSON.stringify({ state: 'blocked', diagnostics: report.state === 'invalid' ? report.diagnostics : [{ code: 'ROUTING_PLAN_INVALID', message: 'Plan must validate before resolution.' }] })}\n` : 'Plan routing: blocked\n');
161
+ process.exitCode = 2;
162
+ return;
163
+ }
164
+ if (options.lineage) {
165
+ if (report.schema !== 'compact-slices/v2') {
166
+ process.stdout.write(options.json ? `${JSON.stringify({ state: 'blocked', diagnostics: [{ code: 'ROUTING_LINEAGE_V1', message: 'Lineage routing is only available for compact v2 plans.' }] })}\n` : 'Plan routing: blocked\n');
167
+ process.exitCode = 2;
168
+ return;
169
+ }
170
+ const observed = journalObservation(cwd);
171
+ const binding = observed.journalState?.planBinding;
172
+ const attempts = observed.journalState?.routingAttempts?.filter((attempt) => attempt.lineageId === options.lineage) ?? [];
173
+ const lineage = observed.journalState?.implementationLineages?.find((candidate) => candidate.id === options.lineage);
174
+ const bound = !observed.journalCorrupt && binding?.digest === report.planDigest && binding.executionDigest === report.executionDigest;
175
+ const lineageMatches = lineage !== undefined && lineage.sliceId === options.slice && lineage.planDigest === report.planDigest && lineage.executionDigest === report.executionDigest && attempts.every((attempt) => attempt.obligationId === lineage.obligationId && attempt.envelope.sliceId === options.slice);
176
+ if (!bound || !lineageMatches || attempts.length > 3 || attempts.some((attempt) => attempt.envelope.planDigest !== report.planDigest || attempt.envelope.executionDigest !== report.executionDigest || ['unknown', 'reserved', 'active'].includes(attempt.state))) {
177
+ process.stdout.write(options.json ? `${JSON.stringify({ state: 'blocked', diagnostics: [{ code: 'ROUTING_LINEAGE_UNBOUND', message: 'Lineage routing requires a current journal binding and non-unknown matching attempts.' }] })}\n` : 'Plan routing: blocked\n');
178
+ process.exitCode = 2;
179
+ return;
180
+ }
181
+ }
182
+ const roles = ['implementer', 'specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'];
183
+ if (!roles.includes(options.role))
184
+ throw new Error('--role is invalid');
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 } }; })();
190
+ process.stdout.write(options.json ? `${JSON.stringify(result)}\n` : `Plan routing: ${result.state}\n`);
191
+ if (result.state === 'blocked')
192
+ process.exitCode = 2;
193
+ });
194
+ plan.command('migration-facts <plan-path>')
195
+ .description('collect read-only durable migration facts')
196
+ .option('--cwd <path>')
197
+ .option('--historical-root <path>', 'admitted sibling #148 worktree')
198
+ .requiredOption('--issue <https-url...>', 'durable issue links, including #126')
199
+ .option('--json')
200
+ .action((planPath, options) => {
201
+ assertText(planPath, 'plan path');
202
+ const cwd = options.cwd ?? process.cwd();
203
+ assertText(cwd, '--cwd');
204
+ const report = options.historicalRoot ? (0, migration_1.collectIssue148HistoricalFacts)(options.historicalRoot, options.issue) : (deps.collectMigrationFacts ?? migration_1.collectMigrationFacts)(planPath, cwd, options.issue);
205
+ process.stdout.write(options.json ? `${JSON.stringify(report)}\n` : `Migration facts: ${report.state}\n`);
206
+ process.exitCode = report.state === 'supported-completion' ? 0 : 2;
207
+ });
81
208
  plan
82
209
  .command('validate <plan-path>')
83
210
  .description('validate a compact plan without modifying it')
@@ -95,4 +222,52 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
95
222
  const code = exitCodeFor(report);
96
223
  process.exitCode = code;
97
224
  });
225
+ plan
226
+ .command('admit <plan-path>')
227
+ .description('read-only fail-closed compact-plan admission')
228
+ .requiredOption('--provider <target>', 'target provider')
229
+ .requiredOption('--cwd <path>', 'repository root for plan containment and source resolution')
230
+ .option('--execution-mode <mode>', 'explicit override: interactivo or desatendido')
231
+ .option('--require-current', 'require authoritative consumed-contract currentness')
232
+ .option('--verify-sensors', 'require an empirical sensor pass')
233
+ .option('--runtime-kind <kind>', 'routing runtime kind for compact v2')
234
+ .option('--runtime-version <version>', 'routing runtime version for compact v2')
235
+ .option('--account-scope-digest <sha>', 'routing account scope digest for compact v2')
236
+ .option('--json', 'emit one stable JSON report')
237
+ .action(async (planPath, options) => {
238
+ assertText(planPath, 'plan path');
239
+ assertText(options.cwd, '--cwd');
240
+ assertText(options.provider, '--provider');
241
+ const admission = deps.admitPlan ?? admission_1.admitPlan;
242
+ const preferences = deps.readPreferences ?? config_1.readPreferences;
243
+ const currentnessCheck = deps.checkCurrentness ?? check_1.checkCurrentness;
244
+ const sensorRun = deps.runSensors ?? run_1.runSensors;
245
+ const registryInventory = deps.listRegistries ?? registries_1.listRegistries;
246
+ const planReport = deps.validatePlanFile(planPath, options.cwd);
247
+ // The mode comes from the same authenticated bytes/digest that produced
248
+ // planReport. Never reopen the path to parse a mutable header.
249
+ const executionMode = options.executionMode === undefined
250
+ ? planReport.state === 'valid' ? (planReport.executionMode ?? 'interactivo') : 'interactivo'
251
+ : options.executionMode === 'desatendido' ? 'desatendido' : options.executionMode === 'interactivo' ? 'interactivo' : options.executionMode;
252
+ const journal = executionMode === 'desatendido' ? journalObservation(options.cwd) : {};
253
+ const normalizedPlanPath = path_1.default.relative(options.cwd, path_1.default.resolve(options.cwd, planPath)).replace(/\\/g, '/');
254
+ const enabledAgents = preferences().enabledAgents;
255
+ // The documented order is plan, provider, currentness, sensors, journal,
256
+ // capabilities. Do not call the full composer early: that would resolve a
257
+ // capability before empirical evidence gates have had their ordered turn.
258
+ const earlyBoundary = planReport.state !== 'valid' || !(0, providers_1.isAgentTarget)(options.provider) || !enabledAgents.includes(options.provider);
259
+ if (earlyBoundary) {
260
+ const report = await admission({ plan: planReport, provider: options.provider, cwd: options.cwd, enabledAgents, executionMode, planPath: normalizedPlanPath, ...journal });
261
+ process.stdout.write(admissionOutput(report, options.json === true));
262
+ process.exitCode = 2;
263
+ return;
264
+ }
265
+ const readRouting = () => { if (!options.runtimeKind || !options.runtimeVersion || !options.accountScopeDigest)
266
+ 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()))
267
+ 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 }; };
268
+ 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 }, { admitPlan: admission, listRegistries: registryInventory, checkCurrentness: currentnessCheck, runSensors: sensorRun, readRouting });
269
+ process.stdout.write(admissionOutput(report, options.json === true));
270
+ if (report.state !== 'admitted')
271
+ process.exitCode = 2;
272
+ });
98
273
  }