agentic-workflow-manager 9.13.2 → 9.14.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.
@@ -166,7 +166,7 @@ function sanitizeAdmissionReport(report) {
166
166
  invalidAdmission();
167
167
  if (value.cliCurrentness !== undefined && !CLI_CURRENTNESS.has(value.cliCurrentness))
168
168
  invalidAdmission();
169
- if (value.sensorEvidence !== undefined && value.sensorEvidence !== 'generation-bound')
169
+ if (value.sensorEvidence !== undefined && value.sensorEvidence !== 'generation-bound' && value.sensorEvidence !== 'r8-registry-opt-out')
170
170
  invalidAdmission();
171
171
  if (value.planDigest !== undefined && (typeof value.planDigest !== 'string' || !/^[a-f0-9]{64}$/.test(value.planDigest)))
172
172
  invalidAdmission();
@@ -225,17 +225,29 @@ async function admitPlan(input) {
225
225
  if (input.compatibilityDiagnostics?.length)
226
226
  return blocked(input, input.compatibilityDiagnostics, { planDigest: plan.planDigest, provider, executionMode: mode, currentness: 'unverifiable', cliCurrentness });
227
227
  }
228
+ let sensorsReported = 'not-required';
229
+ let sensorEvidence = {};
228
230
  if (input.verifySensors) {
229
231
  if (!input.sensors)
230
232
  return blocked(input, [diagnostic('ADMISSION_SENSORS_REQUIRED', 'Sensor evidence was required but not supplied.')], { planDigest: plan.planDigest, provider, executionMode: mode, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness });
231
233
  const sensors = sensorVerdict(input.sensors);
232
- if (sensors !== 'pass')
234
+ const registryOptOut = sensors === 'not-certified' && input.sensorClosurePolicy === 'r8-registry-opt-out' && input.sensors.mode === 'opt-out';
235
+ if (sensors !== 'pass' && !registryOptOut)
233
236
  return blocked(input, [diagnostic('ADMISSION_SENSORS_BLOCKED', `Sensor verdict is ${sensors}.`)], { planDigest: plan.planDigest, provider, executionMode: mode, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness, sensors });
237
+ sensorsReported = sensors;
238
+ if (registryOptOut)
239
+ sensorEvidence = { sensorEvidence: 'r8-registry-opt-out' };
234
240
  }
235
241
  if (mode === 'desatendido') {
236
- const journal = journalStatus(input, plan);
237
- if (journal !== 'current')
238
- return blocked(input, [diagnostic('ADMISSION_JOURNAL_BINDING_REQUIRED', 'Unattended execution requires a healthy schema-2 journal binding for this exact plan; run watch --init --plan.')], { planDigest: plan.planDigest, provider, executionMode: mode, journal, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness, sensors: input.verifySensors ? 'pass' : 'not-required' });
242
+ // The durable controller (journal + awm watch + jobs) is opt-in, as it
243
+ // was before 9.8.0: native v1 dispatch with no journal on its branch
244
+ // runs as one provider session. Creating a journal is the opt-in, and
245
+ // from then on it must be current. v2 routing custody lives in the
246
+ // journal, so awm-routed work still requires it.
247
+ const observed = journalStatus(input, plan);
248
+ const journal = observed === 'missing' && plan.schema === 'compact-slices/v1' ? 'not-required' : observed;
249
+ if (journal !== 'current' && journal !== 'not-required')
250
+ return blocked(input, [diagnostic('ADMISSION_JOURNAL_BINDING_REQUIRED', 'Unattended execution requires a healthy schema-2 journal binding for this exact plan; run watch --init --plan.')], { planDigest: plan.planDigest, provider, executionMode: mode, journal, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness, sensors: sensorsReported, ...sensorEvidence });
239
251
  const capabilities = unattendedCapabilities(provider, input.controllerAutonomy);
240
252
  const resolution = { outcome: capabilities.unattendedController === 'supported' ? 'native' : 'blocked', provider, capabilities, evidenceVersion: 'r1-v1', diagnostics: [] };
241
253
  if (resolution.outcome === 'blocked') {
@@ -246,17 +258,17 @@ async function admitPlan(input) {
246
258
  const reason = exports.PROVIDER_EXECUTION_CAPABILITIES[provider].unattendedController !== 'supported'
247
259
  ? diagnostic('ADMISSION_CAPABILITY_UNVERIFIED', `Provider ${provider} has no verified unattended execution capability.`)
248
260
  : diagnostic('ADMISSION_CONTROLLER_AUTONOMY_REQUIRED', `Unattended dispatch on ${provider} requires an explicit controller autonomy posture; without it the controller blocks on its first tool call.`);
249
- return blocked(input, [reason], { planDigest: plan.planDigest, provider, executionMode: mode, journal, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness, sensors: input.verifySensors ? 'pass' : 'not-required', capabilityResolution: resolution });
261
+ return blocked(input, [reason], { planDigest: plan.planDigest, provider, executionMode: mode, journal, currentness: input.requireCurrent ? 'current' : 'not-checked', cliCurrentness, sensors: sensorsReported, ...sensorEvidence, capabilityResolution: resolution });
250
262
  }
251
263
  const routing = plan.schema === 'compact-slices/v2' ? input.routing ?? await input.routingReader?.() : input.routing;
252
- return completeAdmission({ ...input, routing, routingReader: undefined }, plan, provider, mode, journal, input.requireCurrent ? 'current' : 'not-checked', input.verifySensors ? 'pass' : 'not-required', resolution, cliCurrentness);
264
+ return { ...completeAdmission({ ...input, routing, routingReader: undefined }, plan, provider, mode, journal, input.requireCurrent ? 'current' : 'not-checked', sensorsReported, resolution, cliCurrentness), ...sensorEvidence };
253
265
  }
254
266
  const capabilities = exports.PROVIDER_EXECUTION_CAPABILITIES[provider];
255
267
  const resolution = { outcome: capabilities.interactiveExecution === 'supported' ? 'native' : 'blocked', provider, capabilities, evidenceVersion: 'r1-v1', diagnostics: [] };
256
268
  if (resolution.outcome === 'blocked')
257
269
  return blocked(input, [diagnostic('ADMISSION_CAPABILITY_UNVERIFIED', `Provider ${provider} has no verified interactive execution capability.`)], { planDigest: plan.planDigest, provider, executionMode: mode, capabilityResolution: resolution });
258
270
  const routing = plan.schema === 'compact-slices/v2' ? input.routing ?? await input.routingReader?.() : input.routing;
259
- return completeAdmission({ ...input, routing, routingReader: undefined }, plan, provider, mode, 'not-required', input.requireCurrent ? 'current' : 'not-checked', input.verifySensors ? 'pass' : 'not-required', resolution, cliCurrentness);
271
+ return { ...completeAdmission({ ...input, routing, routingReader: undefined }, plan, provider, mode, 'not-required', input.requireCurrent ? 'current' : 'not-checked', sensorsReported, resolution, cliCurrentness), ...sensorEvidence };
260
272
  }
261
273
  function completeAdmission(input, plan, provider, executionMode, journal, currentness, sensors, capabilityResolution, cliCurrentness = 'not-checked') {
262
274
  if (plan.schema === 'compact-slices/v1')
@@ -271,6 +271,17 @@ function hasOwnedGenerationAdmission(input) {
271
271
  || (generation.wrapperRef !== undefined && generation.wrapperRef.spawnNonce === generation.spawnNonce
272
272
  && (0, process_1.refIsAlive)(generation.wrapperRef));
273
273
  }
274
+ /** R8: only a content registry (its manifest at the sensor project root) may close on a deliberate opt-out. */
275
+ function registryOptOutPolicy(sensors) {
276
+ if (sensors?.mode !== 'opt-out' || !sensors.projectRoot)
277
+ return undefined;
278
+ try {
279
+ return fs_1.default.statSync(path_1.default.join(sensors.projectRoot, registries_1.REGISTRY_MANIFEST_NAME)).isFile() ? 'r8-registry-opt-out' : undefined;
280
+ }
281
+ catch {
282
+ return undefined;
283
+ }
284
+ }
274
285
  /** One admission authority for the public command AND actual watch dispatch.
275
286
  * No empirical execution occurs before provenance/currentness/CLI-floor gates. */
276
287
  async function admitRegistryPlan(input, dependencies = {}) {
@@ -308,8 +319,9 @@ async function admitRegistryPlan(input, dependencies = {}) {
308
319
  return scope.provenance === 'proven';
309
320
  };
310
321
  let generationBound = false;
322
+ let sensorClosurePolicy;
311
323
  const compose = async (fields) => {
312
- const report = await admission({ ...input, provenance: scope.provenance, consumedRegistryComponents: scope.consumedRegistryComponents, ...fields });
324
+ const report = await admission({ ...input, provenance: scope.provenance, consumedRegistryComponents: scope.consumedRegistryComponents, ...(sensorClosurePolicy ? { sensorClosurePolicy } : {}), ...fields });
313
325
  const withEvidence = generationBound && report.sensors === 'pass' ? { ...report, sensorEvidence: 'generation-bound' } : report;
314
326
  if (scope.provenanceDiagnostic && report.diagnostics.some(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED')) {
315
327
  return { ...withEvidence, diagnostics: report.diagnostics.map(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED' ? scope.provenanceDiagnostic : diagnostic) };
@@ -337,6 +349,7 @@ async function admitRegistryPlan(input, dependencies = {}) {
337
349
  const sensors = input.verifySensors ? input.sensors ?? (generationBound
338
350
  ? { sensors: [], overall: 'pass', reason: 'generation-bound-admission' }
339
351
  : await (dependencies.runSensors ?? run_1.runSensors)({ cwd: input.cwd, all: true, readOnly: true })) : undefined;
352
+ sensorClosurePolicy = registryOptOutPolicy(sensors);
340
353
  if (input.requireCurrent && input.verifySensors) {
341
354
  if (!refresh())
342
355
  return compose({ currentness, sensors });
@@ -309,7 +309,33 @@ describe('plan admit Commander wiring', () => {
309
309
  process.exitCode = undefined;
310
310
  }
311
311
  });
312
- it('derives desatendido from the canonical plan header and requires a journal without a flag', async () => {
312
+ it.each([
313
+ ['a content registry', true, 'admitted'],
314
+ ['an ordinary project', false, 'blocked'],
315
+ ])('applies the R8 sensor opt-out exception only to %s', async (_name, isRegistry, expected) => {
316
+ const root = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-admit-r8-')));
317
+ const output = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
318
+ if (isRegistry)
319
+ fs_1.default.writeFileSync(path_1.default.join(root, 'awm-registry.json'), JSON.stringify({ minCliVersion: '1.0.0' }));
320
+ const sensors = jest.fn().mockResolvedValue({ overall: 'not_certified', sensors: [], mode: 'opt-out', reason: 'opt-out-declared', projectRoot: root, manifestPath: path_1.default.join(root, '.awm', 'sensors.json') });
321
+ const program = new commander_1.Command();
322
+ (0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => valid, listRegistries: () => [], readPreferences: () => ({ defaultAgent: 'codex', enabledAgents: ['codex'], installMethod: 'symlink', defaultScope: 'local' }), runSensors: sensors });
323
+ try {
324
+ await program.parseAsync(['node', 'awm', 'plan', 'admit', 'plan.md', '--provider', 'codex', '--cwd', root, '--execution-mode', 'interactivo', '--verify-sensors', '--json']);
325
+ const report = JSON.parse(String(output.mock.calls.at(-1)[0]));
326
+ expect(report).toMatchObject({ state: expected, sensors: 'not-certified' });
327
+ if (isRegistry)
328
+ expect(report.sensorEvidence).toBe('r8-registry-opt-out');
329
+ else
330
+ expect(report.diagnostics[0].code).toBe('ADMISSION_SENSORS_BLOCKED');
331
+ }
332
+ finally {
333
+ output.mockRestore();
334
+ fs_1.default.rmSync(root, { recursive: true, force: true });
335
+ process.exitCode = undefined;
336
+ }
337
+ });
338
+ it('derives desatendido from the canonical plan header; a native v1 plan needs no journal (opt-in durable custody)', async () => {
313
339
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-admit-header-'));
314
340
  const output = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
315
341
  try {
@@ -327,7 +353,11 @@ describe('plan admit Commander wiring', () => {
327
353
  readPreferences: () => ({ defaultAgent: 'codex', enabledAgents: ['codex'], installMethod: 'symlink', defaultScope: 'local' }),
328
354
  });
329
355
  await program.parseAsync(['node', 'awm', 'plan', 'admit', 'plan.md', '--provider', 'codex', '--cwd', root, '--json']);
330
- expect(JSON.parse(String(output.mock.calls[0][0]))).toMatchObject({ state: 'blocked', executionMode: 'desatendido', journal: expect.stringMatching(/missing|corrupt/) });
356
+ const withoutPosture = JSON.parse(String(output.mock.calls[0][0]));
357
+ expect(withoutPosture).toMatchObject({ state: 'blocked', executionMode: 'desatendido', journal: 'not-required' });
358
+ expect(withoutPosture.diagnostics[0]).toMatchObject({ code: 'ADMISSION_CONTROLLER_AUTONOMY_REQUIRED' });
359
+ await program.parseAsync(['node', 'awm', 'plan', 'admit', 'plan.md', '--provider', 'codex', '--cwd', root, '--controller-autonomy', 'approval-free', '--json']);
360
+ expect(JSON.parse(String(output.mock.calls[1][0]))).toMatchObject({ state: 'admitted', executionMode: 'desatendido', journal: 'not-required' });
331
361
  }
332
362
  finally {
333
363
  output.mockRestore();
@@ -161,21 +161,30 @@ describe('runCommand — exit codes and output', () => {
161
161
  });
162
162
  itLinux('never lets a structured capture file grow beyond maxBuffer', async () => {
163
163
  const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-exec-cap-'));
164
- const marker = path_1.default.join(dir, 'written');
165
- const existing = new Set(fs_1.default.readdirSync(os_1.default.tmpdir()).filter(name => name.startsWith('awm-sensor-output-')));
164
+ // The overflow watcher kills the child and deletes the capture as soon as
165
+ // the cap is reached, so polling for the file races that cleanup. Capture
166
+ // files only grow, so their size at deletion is the largest they ever were.
167
+ const captureSizes = [];
168
+ const rmSync = fs_1.default.rmSync.bind(fs_1.default);
169
+ const removal = jest.spyOn(fs_1.default, 'rmSync').mockImplementation((target, options) => {
170
+ if (typeof target === 'string' && path_1.default.basename(target).startsWith('awm-sensor-output-')) {
171
+ captureSizes.push(fs_1.default.statSync(path_1.default.join(target, 'stdout')).size);
172
+ }
173
+ return rmSync(target, options);
174
+ });
166
175
  try {
167
- const result = (0, exec_1.runStructuredCommand)({
176
+ const result = await (0, exec_1.runStructuredCommand)({
168
177
  executable: 'node',
169
178
  resolution: 'path',
170
- args: ['-e', `const fs = require('fs'); try { fs.writeSync(1, Buffer.alloc(8192, 'x')); } catch {} fs.writeFileSync(${JSON.stringify(marker)}, 'written'); setTimeout(() => {}, 1000);`],
179
+ args: ['-e', `const fs = require('fs'); try { fs.writeSync(1, Buffer.alloc(8192, 'x')); } catch {} setTimeout(() => {}, 1000);`],
171
180
  }, { timeout: 5_000, cwd: dir, maxBuffer: 1_024 });
172
- expect(await until(() => fs_1.default.existsSync(marker))).toBe(true);
173
- const capture = fs_1.default.readdirSync(os_1.default.tmpdir()).find(name => name.startsWith('awm-sensor-output-') && !existing.has(name));
174
- expect(capture).toBeDefined();
175
- expect(fs_1.default.statSync(path_1.default.join(os_1.default.tmpdir(), capture, 'stdout')).size).toBeLessThanOrEqual(1_024);
176
- await expect(result).resolves.toMatchObject({ overflowed: true });
181
+ expect(result).toMatchObject({ overflowed: true });
182
+ expect(captureSizes).toHaveLength(1);
183
+ expect(captureSizes[0]).toBeGreaterThan(0);
184
+ expect(captureSizes[0]).toBeLessThanOrEqual(1_024);
177
185
  }
178
186
  finally {
187
+ removal.mockRestore();
179
188
  fs_1.default.rmSync(dir, { recursive: true, force: true });
180
189
  }
181
190
  });
@@ -143,12 +143,79 @@ describe('admitPlan', () => {
143
143
  expect(report).toMatchObject({ state: 'blocked', executionMode: 'interactivo', provider: 'cursor', journal: 'not-required' });
144
144
  expect(report.diagnostics[0].code).toBe('ADMISSION_CAPABILITY_UNVERIFIED');
145
145
  });
146
- it('reports the schema-2 journal prerequisite for otherwise admissible unattended work', async () => {
147
- const unattended = { ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } };
148
- const report = await (0, admission_1.admitPlan)({ plan: unattended, provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'] });
149
- expect(report).toMatchObject({ state: 'blocked', executionMode: 'desatendido', journal: 'missing' });
150
- expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_JOURNAL_BINDING_REQUIRED' });
151
- expect(report.forecast).toBeUndefined();
146
+ // The durable controller (journal + awm watch + jobs) is opt-in again for
147
+ // native v1 dispatch, as it was before 9.8.0: a v1 unattended plan with no
148
+ // journal on its branch runs as one native provider session. Creating a
149
+ // journal (awm watch --init) is the opt-in, after which it must be current.
150
+ // v2 routing custody lives in the journal, so awm-routed still requires it.
151
+ describe('the durable journal is opt-in for native v1 unattended work', () => {
152
+ const unattended = () => ({ ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } });
153
+ const base = () => ({ plan: unattended(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], planPath: 'docs/plan.md', controllerAutonomy: 'approval-free' });
154
+ it('admits a v1 unattended plan with no journal as journal not-required', async () => {
155
+ const report = await (0, admission_1.admitPlan)(base());
156
+ expect(report).toMatchObject({ state: 'admitted', executionMode: 'desatendido', journal: 'not-required' });
157
+ expect(report.diagnostics.map(d => d.code)).not.toContain('ADMISSION_JOURNAL_BINDING_REQUIRED');
158
+ });
159
+ it('keeps a journal that exists (the opt-in) fail-closed when it is corrupt or stale', async () => {
160
+ const corrupt = await (0, admission_1.admitPlan)({ ...base(), journalCorrupt: true });
161
+ expect(corrupt).toMatchObject({ state: 'blocked', journal: 'corrupt' });
162
+ expect(corrupt.diagnostics[0]).toMatchObject({ code: 'ADMISSION_JOURNAL_BINDING_REQUIRED' });
163
+ const stale = await (0, admission_1.admitPlan)({ ...base(), journalState: { ...(0, types_1.emptyState)('main'), schema: 2, planBinding: { path: 'docs/plan.md', digest: 'b'.repeat(64), schema: 'compact-slices/v1', executionMode: 'desatendido', boundAt: '2026-09-15T00:00:00.000Z' } } });
164
+ expect(stale).toMatchObject({ state: 'blocked', journal: 'stale' });
165
+ const legacy = await (0, admission_1.admitPlan)({ ...base(), journalState: (0, types_1.emptyState)('main') });
166
+ expect(legacy).toMatchObject({ state: 'blocked', journal: 'stale' });
167
+ });
168
+ it('still requires the journal for awm-routed v2 unattended work', async () => {
169
+ const v2Unattended = { ...valid, schema: 'compact-slices/v2', manifest: { ...valid.manifest, executionMode: 'desatendido', schema: 'compact-slices/v2', slices: valid.manifest.slices.map(slice => ({ ...slice, implementerProfile: 'mechanical' })) } };
170
+ const report = await (0, admission_1.admitPlan)({ ...base(), plan: v2Unattended });
171
+ expect(report).toMatchObject({ state: 'blocked', executionMode: 'desatendido', journal: 'missing' });
172
+ expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_JOURNAL_BINDING_REQUIRED' });
173
+ expect(report.forecast).toBeUndefined();
174
+ });
175
+ it('keeps the controller posture gate for journal-less unattended work', async () => {
176
+ const report = await (0, admission_1.admitPlan)({ ...base(), controllerAutonomy: undefined });
177
+ expect(report.state).toBe('blocked');
178
+ expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_CONTROLLER_AUTONOMY_REQUIRED' });
179
+ });
180
+ });
181
+ // R8: a content registry whose sensors are ALL deliberately disabled may close
182
+ // work; its release proof lives in CI. The local verdict stays not-certified —
183
+ // never reported as pass — and the exception is named in the evidence.
184
+ describe('the R8 registry opt-out closure exception', () => {
185
+ const optOut = { overall: 'not_certified', sensors: [], mode: 'opt-out', reason: 'opt-out-declared', projectRoot: '/r', manifestPath: '/r/.awm/sensors.json' };
186
+ const unattended = () => ({ ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } });
187
+ const base = () => ({ plan: valid, provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], verifySensors: true, sensors: optOut, sensorClosurePolicy: 'r8-registry-opt-out' });
188
+ it('admits interactive work without reporting the opt-out as a pass', async () => {
189
+ const report = await (0, admission_1.admitPlan)(base());
190
+ expect(report).toMatchObject({ state: 'admitted', sensors: 'not-certified', sensorEvidence: 'r8-registry-opt-out' });
191
+ });
192
+ it('admits journal-less unattended work under the same exception', async () => {
193
+ const report = await (0, admission_1.admitPlan)({ ...base(), plan: unattended(), planPath: 'docs/plan.md', controllerAutonomy: 'approval-free' });
194
+ expect(report).toMatchObject({ state: 'admitted', executionMode: 'desatendido', journal: 'not-required', sensors: 'not-certified', sensorEvidence: 'r8-registry-opt-out' });
195
+ });
196
+ it('reports the preserved verdict when a later gate blocks', async () => {
197
+ const report = await (0, admission_1.admitPlan)({ ...base(), plan: unattended(), planPath: 'docs/plan.md' });
198
+ expect(report).toMatchObject({ state: 'blocked', sensors: 'not-certified', sensorEvidence: 'r8-registry-opt-out' });
199
+ expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_CONTROLLER_AUTONOMY_REQUIRED' });
200
+ });
201
+ it.each([
202
+ ['no declared policy', { sensorClosurePolicy: undefined }],
203
+ ['a failing sensor', { sensors: { ...optOut, overall: 'fail', mode: 'project-sensors' } }],
204
+ ['an absent manifest', { sensors: { ...optOut, mode: 'missing', reason: 'manifest-absent' } }],
205
+ ['a native gate', { sensors: { ...optOut, mode: 'native-gate', reason: 'native-gate-declared' } }],
206
+ ['an unavailable sensor source', { sensors: { ...optOut, mode: 'source-unavailable' } }],
207
+ ['all-disabled legacy sensors', { sensors: { ...optOut, overall: 'skipped', mode: 'project-sensors' } }],
208
+ ])('keeps blocking with %s', async (_name, fields) => {
209
+ const report = await (0, admission_1.admitPlan)({ ...base(), ...fields });
210
+ expect(report.state).toBe('blocked');
211
+ expect(report.diagnostics[0]).toMatchObject({ code: 'ADMISSION_SENSORS_BLOCKED' });
212
+ expect(report.sensorEvidence).toBeUndefined();
213
+ });
214
+ it('accepts only the known sensor evidence values at the public sanitizer', () => {
215
+ const report = { state: 'admitted', planState: 'valid', journal: 'not-required', currentness: 'not-checked', sensors: 'not-certified', diagnostics: [] };
216
+ expect((0, admission_1.sanitizeAdmissionReport)({ ...report, sensorEvidence: 'r8-registry-opt-out' }).sensorEvidence).toBe('r8-registry-opt-out');
217
+ expect(() => (0, admission_1.sanitizeAdmissionReport)({ ...report, sensorEvidence: 'opt-out' })).toThrow('invalid report');
218
+ });
152
219
  });
153
220
  it('admits unattended work only with an exact schema-2 plan binding', async () => {
154
221
  const unattended = { ...valid, manifest: { ...valid.manifest, executionMode: 'desatendido' } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.13.2",
3
+ "version": "9.14.1",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
@@ -65,7 +65,7 @@
65
65
  "dependency-cruiser": "16.10.4",
66
66
  "eslint": "10.8.1",
67
67
  "jest": "^30.2.0",
68
- "node-gyp": "^11.5.0",
68
+ "node-gyp": "^12.4.0",
69
69
  "ts-jest": "^29.4.6",
70
70
  "ts-node": "^10.9.2",
71
71
  "typescript": "^5.9.3"
Binary file
Binary file