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.
- package/dist/src/core/admission/index.js +20 -8
- package/dist/src/core/admission/registry-contracts.js +14 -1
- package/dist/tests/commands/plan/index.test.js +32 -2
- package/dist/tests/commands/sensors/exec.test.js +18 -9
- package/dist/tests/core/admission/index.test.js +73 -6
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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:
|
|
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',
|
|
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',
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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 {}
|
|
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(
|
|
173
|
-
|
|
174
|
-
expect(
|
|
175
|
-
expect(
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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.
|
|
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": "^
|
|
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
|
|
Binary file
|
|
Binary file
|