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
@@ -13,6 +13,8 @@ const reconcile_1 = require("../../../src/commands/job/reconcile");
13
13
  const reap_1 = require("../../../src/commands/job/reap");
14
14
  const job_1 = require("../../../src/commands/job");
15
15
  const watch_1 = require("../../../src/commands/watch");
16
+ const init_1 = require("../../../src/commands/watch/init");
17
+ const validate_1 = require("../../../src/core/plan/validate");
16
18
  const types_1 = require("../../../src/core/journal/types");
17
19
  const store_1 = require("../../../src/core/journal/store");
18
20
  const descriptor_1 = require("../../../src/core/tracks/descriptor");
@@ -53,6 +55,22 @@ function passingState() {
53
55
  return s;
54
56
  }
55
57
  describe('gate', () => {
58
+ test('un verdict routed exige intento observado, identidad y fingerprint coincidentes', () => {
59
+ const s = passingState();
60
+ s.verdicts[0] = { ...s.verdicts[0], routingAttemptId: 'route-1' };
61
+ let gate = (0, gate_1.computeGate)(s, false, fpCurrent);
62
+ expect(gate.reasons.some((reason) => reason.category === 'routing-evidence')).toBe(true);
63
+ s.routingAttempts = [{
64
+ schema: 'routing-attempt/v1', id: 'route-1', obligationId: 'o-spec', lineageId: 'lineage-1', attempt: 1,
65
+ envelope: { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'specification-reviewer', requestedProfile: 'full', effectiveProfile: 'full', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) },
66
+ envelopeDigest: 'a'.repeat(64), fingerprint: 'fp', state: 'active', nativeAgentId: 'native-1', observed: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } },
67
+ }];
68
+ s.planBinding = { path: 'plan.md', digest: 'c'.repeat(64), executionDigest: 'd'.repeat(64), executionIdentitySchema: 'awm-plan-execution/v1', schema: 'compact-slices/v2', executionMode: 'desatendido', boundAt: 'now' };
69
+ expect((0, gate_1.computeGate)(s, false, fpCurrent).pass).toBe(true);
70
+ s.routingAttempts[0].fingerprint = 'different';
71
+ gate = (0, gate_1.computeGate)(s, false, fpCurrent);
72
+ expect(gate.reasons.some((reason) => reason.category === 'routing-evidence')).toBe(true);
73
+ });
56
74
  test('el estado de referencia pasa; la corrupcion bloquea (R3.2)', () => {
57
75
  expect((0, gate_1.computeGate)(passingState(), false, fpCurrent).pass).toBe(true);
58
76
  const g = (0, gate_1.computeGate)(null, true, fpCurrent);
@@ -501,6 +519,16 @@ function trackRefFixture(overrides, worktreePath) {
501
519
  function descriptorFixture(overrides, planRoot) {
502
520
  return { schema: 1, planRoot, planBranch: 'main', trackId: 'cli', planJournalId: 'j-1', fencingToken: 'f'.repeat(32), ...overrides };
503
521
  }
522
+ function initCompactJournal(repo, branch) {
523
+ const planPath = path_1.default.join(repo, 'plans', 'fixture.md');
524
+ fs_1.default.mkdirSync(path_1.default.dirname(planPath), { recursive: true });
525
+ fs_1.default.writeFileSync(planPath, fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8'));
526
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'source.md'), '## Canonical source\nfixture source\n');
527
+ const report = (0, validate_1.validatePlanFile)('plans/fixture.md', repo);
528
+ if (report.state !== 'valid')
529
+ throw new Error(`compact fixture must validate: ${JSON.stringify(report)}`);
530
+ (0, init_1.initWatch)(repo, branch, { path: 'plans/fixture.md', report });
531
+ }
504
532
  async function runCommand(register, argv, cwd) {
505
533
  const prog = new commander_1.Command();
506
534
  prog.exitOverride();
@@ -543,12 +571,12 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
543
571
  gitInitAt(planRoot, 'main');
544
572
  gitInitAt(trackRoot, 'track/cli');
545
573
  gitInitAt(otherRoot, 'track/cli');
546
- (0, store_1.initJournal)(planRoot, 'main');
574
+ initCompactJournal(planRoot, 'main');
547
575
  const planState = (0, store_1.readJournal)(planRoot, 'main').state;
548
576
  planState.journalId = 'j-1';
549
577
  planState.tracks = [trackRefFixture({ fencingToken: 'f'.repeat(32) }, trackRoot)];
550
578
  (0, store_1.writeJournal)(planRoot, 'main', planState);
551
- (0, store_1.initJournal)(trackRoot, 'track/cli');
579
+ initCompactJournal(trackRoot, 'track/cli');
552
580
  const trackState = (0, store_1.readJournal)(trackRoot, 'track/cli').state;
553
581
  trackState.trackContext = { trackId: 'cli', taskIds: [], planDigest: 'x', baseSha: 'y', planJournalId: 'j-1' };
554
582
  (0, store_1.writeJournal)(trackRoot, 'track/cli', trackState);
@@ -556,7 +584,7 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
556
584
  // otherRoot: mismo descriptor (mismo plan/trackId/fencing) pero NO es
557
585
  // el worktree que el TrackRef del plan declara — su realpath jamas
558
586
  // puede autenticar (R9.4), aunque el resto del descriptor "calce".
559
- (0, store_1.initJournal)(otherRoot, 'track/cli');
587
+ initCompactJournal(otherRoot, 'track/cli');
560
588
  const otherState = (0, store_1.readJournal)(otherRoot, 'track/cli').state;
561
589
  otherState.trackContext = { trackId: 'cli', taskIds: [], planDigest: 'x', baseSha: 'y', planJournalId: 'j-1' };
562
590
  (0, store_1.writeJournal)(otherRoot, 'track/cli', otherState);
@@ -580,24 +608,26 @@ describe('guard de contexto en awm job / awm watch (R9.4)', () => {
580
608
  expect(exitCode).toBe(1);
581
609
  expect(JSON.parse(out).pass).toBe(false);
582
610
  });
583
- test('`watch --init` desde un cwd SIN descriptor (repo comun, sin tracks) procede exactamente como antes de esta task', async () => {
611
+ test('`watch --init --plan` desde un cwd SIN descriptor crea un binding compacto', async () => {
584
612
  const plainRepo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-guard-plain-'));
585
613
  try {
586
614
  gitInitAt(plainRepo, 'main');
587
- const { out, err, exitCode } = await runWatch(['--init'], plainRepo);
615
+ const planPath = path_1.default.join(plainRepo, 'plans', 'fixture.md');
616
+ fs_1.default.mkdirSync(path_1.default.dirname(planPath), { recursive: true });
617
+ fs_1.default.writeFileSync(planPath, fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8'));
618
+ fs_1.default.writeFileSync(path_1.default.join(plainRepo, 'source.md'), '## Canonical source\nfixture source\n');
619
+ const { out, err, exitCode } = await runWatch(['--init', '--plan', 'plans/fixture.md'], plainRepo);
588
620
  expect(exitCode).toBeNull();
589
621
  expect(err).toBe('');
590
622
  expect(out).toContain('journal inicializado');
623
+ expect((0, store_1.readJournal)(plainRepo, 'main').state).toMatchObject({ schema: 2, planBinding: { path: 'plans/fixture.md', executionMode: 'desatendido' } });
591
624
  }
592
625
  finally {
593
626
  fs_1.default.rmSync(plainRepo, { recursive: true, force: true });
594
627
  }
595
628
  });
596
- test('`watch --init` desde el cwd autenticado (trackRoot) procede con normalidad', async () => {
597
- const { out, err, exitCode } = await runWatch(['--init'], trackRoot);
598
- expect(err).not.toContain('cwd no autenticado');
599
- expect(exitCode).toBeNull();
600
- expect(out).toContain('journal inicializado');
629
+ test('`watch --init --plan` desde el cwd autenticado alcanza la guarda de lifecycle y rechaza sobrescribir el binding', async () => {
630
+ await expect(runWatch(['--init', '--plan', 'plans/fixture.md'], trackRoot)).rejects.toThrow(/overwrite|sobrescribir/i);
601
631
  });
602
632
  test('`watch --init` desde un cwd cuyo descriptor no autentica rechaza con "cwd no autenticado"', async () => {
603
633
  const { err, exitCode } = await runWatch(['--init'], otherRoot);
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const gate_1 = require("../../../src/commands/job/gate");
4
+ const types_1 = require("../../../src/core/journal/types");
5
+ test('routed evidence cannot certify against a legacy journal without its attempt', () => {
6
+ const state = (0, types_1.emptyState)('main');
7
+ state.verdicts.push({ id: 'v1', obligationId: 'o1', result: 'pass', detail: '', receivedAt: 'now', fingerprint: 'fp', argv: [], paths: [], cwd: '.', routingAttemptId: 'missing' });
8
+ expect((0, gate_1.computeGate)(state, false, () => 'fp').reasons).toContainEqual(expect.objectContaining({ category: 'routing-evidence' }));
9
+ });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const child_process_1 = require("child_process");
10
+ const commander_1 = require("commander");
11
+ const job_1 = require("../../../src/commands/job");
12
+ const store_1 = require("../../../src/core/journal/store");
13
+ const journal_1 = require("../../../src/core/model-policy/journal");
14
+ const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'model-private' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
15
+ function gitInit(repo) {
16
+ (0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'init', '-q', '-b', 'main'], { cwd: repo });
17
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'fixture'), 'x');
18
+ (0, child_process_1.execFileSync)('git', ['add', '.'], { cwd: repo });
19
+ (0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'commit', '-qm', 'fixture'], { cwd: repo });
20
+ }
21
+ test('`job routing-report --json` emits only aggregate routing evidence', async () => {
22
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-routing-report-'));
23
+ try {
24
+ gitInit(repo);
25
+ (0, store_1.initJournal)(repo, 'main');
26
+ const state = (0, store_1.readJournal)(repo, 'main').state;
27
+ const reserved = (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'obligation', lineageId: 'lineage', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z');
28
+ (0, store_1.writeJournal)(repo, 'main', (0, journal_1.observeRoutingAttempt)(reserved.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-private' }, '2026-09-17T00:01:00.000Z'));
29
+ const program = new commander_1.Command();
30
+ program.exitOverride();
31
+ (0, job_1.registerJobCommand)(program);
32
+ const out = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
33
+ const cwd = jest.spyOn(process, 'cwd').mockReturnValue(repo);
34
+ await program.parseAsync(['node', 'awm', 'job', 'routing-report', '--json']);
35
+ const rendered = String(out.mock.calls[0][0]);
36
+ const report = JSON.parse(rendered);
37
+ expect(report).toMatchObject({ schema: 'routing-report/v1', attempts: 1, plannedByRole: { implementer: 1 }, actualByRole: { implementer: 1 }, byState: { active: 1 } });
38
+ expect(rendered).not.toContain('native-private');
39
+ expect(rendered).not.toContain('model-private');
40
+ cwd.mockRestore();
41
+ out.mockRestore();
42
+ }
43
+ finally {
44
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
45
+ }
46
+ });
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("../../../src/core/journal/types");
4
+ const journal_1 = require("../../../src/core/model-policy/journal");
5
+ const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
6
+ test('routing report remains read-only across an observed attempt', () => {
7
+ const reserved = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'o1', lineageId: 'l1', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z');
8
+ const observed = (0, journal_1.observeRoutingAttempt)(reserved.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-id' }, '2026-09-17T00:01:00.000Z');
9
+ expect((0, journal_1.routingReport)(observed)).toMatchObject({ attempts: 1, byState: { active: 1 } });
10
+ expect(observed.routingAttempts[0].nativeAgentId).toBe('native-id');
11
+ });
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const model_policy_1 = require("../../../src/commands/model-policy");
5
+ const out = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
6
+ describe('model-policy command', () => {
7
+ beforeEach(() => { jest.clearAllMocks(); process.exitCode = undefined; });
8
+ afterAll(() => out.mockRestore());
9
+ it('emits a read-only protocol contract as JSON', async () => {
10
+ const program = new commander_1.Command();
11
+ program.exitOverride();
12
+ program.configureOutput({ writeErr: () => undefined });
13
+ (0, model_policy_1.registerModelPolicyCommand)(program);
14
+ await program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--json']);
15
+ expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({
16
+ schema: 'routing-protocol/v1',
17
+ supportedPlanSchemas: ['compact-slices/v1', 'compact-slices/v2'],
18
+ implementerProfiles: ['mechanical', 'integration', 'judgment'],
19
+ limits: { maxFileBytes: 262144 },
20
+ });
21
+ });
22
+ it('rejects missing values instead of silently accepting an incomplete approval', async () => {
23
+ const program = new commander_1.Command();
24
+ program.exitOverride();
25
+ program.configureOutput({ writeErr: () => undefined });
26
+ (0, model_policy_1.registerModelPolicyCommand)(program);
27
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'approve', '--file', '--scope', 'user', '--expected-digest', 'a'.repeat(64)])).rejects.toThrow();
28
+ });
29
+ it('accepts and validates --cwd on contract, and resolves status through it', async () => {
30
+ const readEffectivePolicy = jest.fn(() => ({ state: 'absent' }));
31
+ const program = new commander_1.Command();
32
+ program.exitOverride();
33
+ program.configureOutput({ writeErr: () => undefined });
34
+ (0, model_policy_1.registerModelPolicyCommand)(program, { readEffectivePolicy });
35
+ await program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--cwd', 'fixture-root', '--json']);
36
+ await program.parseAsync(['node', 'awm', 'model-policy', 'status', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--cwd', 'fixture-root', '--json']);
37
+ expect(readEffectivePolicy).toHaveBeenCalledWith('fixture-root');
38
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--cwd', '--json'])).rejects.toThrow();
39
+ });
40
+ it('reports receipt provenance in status and rejects stale receipt state', async () => {
41
+ const readCapabilities = jest.fn(() => ({ state: 'stale', reason: 'expired' }));
42
+ const program = new commander_1.Command();
43
+ program.exitOverride();
44
+ program.configureOutput({ writeErr: () => undefined });
45
+ (0, model_policy_1.registerModelPolicyCommand)(program, { readEffectivePolicy: () => ({ state: 'absent' }), readCapabilities });
46
+ await program.parseAsync(['node', 'awm', 'model-policy', 'status', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--json']);
47
+ expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({ policy: { state: 'absent' }, capability: { state: 'stale' } });
48
+ expect(process.exitCode).toBe(2);
49
+ });
50
+ it('rejects an empty replacement digest for capability approval at the command boundary', async () => {
51
+ const program = new commander_1.Command();
52
+ program.exitOverride();
53
+ program.configureOutput({ writeErr: () => undefined });
54
+ (0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: jest.fn() });
55
+ await expect(program.parseAsync(['node', 'awm', 'model-policy', 'capabilities', 'approve', '--file', 'receipt.json', '--expected-digest', 'a'.repeat(64), '--replace-digest', ''])).rejects.toThrow(/requires a non-empty value/);
56
+ });
57
+ });
@@ -7,30 +7,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const os_1 = __importDefault(require("os"));
10
+ let tmpHome;
11
+ let originalHome;
12
+ let originalAwmHome;
13
+ beforeEach(() => {
14
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pin-home-'));
15
+ originalHome = process.env.HOME;
16
+ originalAwmHome = process.env.AWM_HOME;
17
+ process.env.HOME = tmpHome;
18
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
19
+ jest.resetModules();
20
+ });
21
+ afterEach(() => {
22
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
23
+ if (originalHome === undefined)
24
+ delete process.env.HOME;
25
+ else
26
+ process.env.HOME = originalHome;
27
+ if (originalAwmHome === undefined)
28
+ delete process.env.AWM_HOME;
29
+ else
30
+ process.env.AWM_HOME = originalAwmHome;
31
+ });
32
+ const readPrefs = () => JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf-8'));
10
33
  describe('pin/unpin (editores de preferences)', () => {
11
- let tmpHome;
12
- let originalHome;
13
- let originalAwmHome;
14
- beforeEach(() => {
15
- tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pin-home-'));
16
- originalHome = process.env.HOME;
17
- originalAwmHome = process.env.AWM_HOME;
18
- process.env.HOME = tmpHome;
19
- process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
20
- jest.resetModules();
21
- });
22
- afterEach(() => {
23
- fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
24
- if (originalHome === undefined)
25
- delete process.env.HOME;
26
- else
27
- process.env.HOME = originalHome;
28
- if (originalAwmHome === undefined)
29
- delete process.env.AWM_HOME;
30
- else
31
- process.env.AWM_HOME = originalAwmHome;
32
- });
33
- const readPrefs = () => JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf-8'));
34
34
  it('setPin escribe pins.base normalizado (acepta prefijo v)', () => {
35
35
  const { setPin } = require('../../src/commands/pin');
36
36
  setPin('base', 'v1.2.0');