agentic-workflow-manager 9.2.1 → 9.4.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.
@@ -10,6 +10,7 @@ const child_process_1 = require("child_process");
10
10
  const checks_1 = require("../../../src/commands/preflight/checks");
11
11
  const preflight_1 = require("../../../src/commands/preflight");
12
12
  const run_1 = require("../../../src/commands/sensors/run");
13
+ const check_1 = require("../../../src/core/currentness/check");
13
14
  // Only `execSync` (used by `resolveOnPath` to check for `gh`/`glab`) is mocked — `git
14
15
  // remote get-url origin` runs for real via `execFileSync` against real tmpdir git repos,
15
16
  // same as every other check in this file exercises the real filesystem.
@@ -21,8 +22,12 @@ jest.mock('../../../src/commands/sensors/run', () => ({
21
22
  ...jest.requireActual('../../../src/commands/sensors/run'),
22
23
  runSensors: jest.fn(),
23
24
  }));
25
+ jest.mock('../../../src/core/currentness/check', () => ({
26
+ checkCurrentness: jest.fn(),
27
+ }));
24
28
  const mockExecSync = child_process_1.execSync;
25
29
  const mockRunSensors = run_1.runSensors;
30
+ const mockCheckCurrentness = check_1.checkCurrentness;
26
31
  /** Turn a tmpdir into a real git repo with (optionally) an `origin` remote. */
27
32
  function gitRepo(dir, remoteUrl) {
28
33
  (0, child_process_1.execFileSync)('git', ['init'], { cwd: dir, stdio: 'pipe' });
@@ -51,19 +56,250 @@ function project(opts = {}) {
51
56
  const dirs = [];
52
57
  const make = (o) => { const d = project(o); dirs.push(d); return d; };
53
58
  afterAll(() => dirs.forEach(d => fs_1.default.rmSync(d, { recursive: true, force: true })));
59
+ let previousAwmHome;
60
+ const awmHomes = [];
61
+ beforeEach(() => {
62
+ previousAwmHome = process.env.AWM_HOME;
63
+ const awmHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-preflight-home-'));
64
+ awmHomes.push(awmHome);
65
+ process.env.AWM_HOME = awmHome;
66
+ });
67
+ afterEach(() => {
68
+ if (previousAwmHome === undefined)
69
+ delete process.env.AWM_HOME;
70
+ else
71
+ process.env.AWM_HOME = previousAwmHome;
72
+ });
73
+ afterAll(() => awmHomes.forEach(dir => fs_1.default.rmSync(dir, { recursive: true, force: true })));
74
+ function installRegistry(manifest, name = 'test-registry') {
75
+ const awmHome = process.env.AWM_HOME;
76
+ const root = path_1.default.join(awmHome, 'registries', name);
77
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills'), { recursive: true });
78
+ const config = fs_1.default.existsSync(path_1.default.join(awmHome, 'registries.json'))
79
+ ? JSON.parse(fs_1.default.readFileSync(path_1.default.join(awmHome, 'registries.json'), 'utf8'))
80
+ : [];
81
+ config.push({ name, remote: `https://example.invalid/${name}.git` });
82
+ fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify(config));
83
+ fs_1.default.writeFileSync(path_1.default.join(root, 'awm-registry.json'), JSON.stringify(manifest));
84
+ }
85
+ function writeValidKernel(dir) {
86
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'AGENTS.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\nagent rules\n<!-- AWM:CONTEXT-KERNEL:END v1 -->\n');
87
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'CONSTITUTION.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\n<!-- awm-context:CTX-PROCESS-001 -->\nprocess rules\n<!-- AWM:CONTEXT-KERNEL:END v1 -->\n');
88
+ const card = path_1.default.join(dir, 'docs', 'awm', 'context', 'releases.md');
89
+ fs_1.default.mkdirSync(path_1.default.dirname(card), { recursive: true });
90
+ fs_1.default.writeFileSync(card, '<!-- awm-context:CTX-RELEASE-001 -->\nrelease rules\n');
91
+ fs_1.default.mkdirSync(path_1.default.join(dir, '.awm', 'context'), { recursive: true });
92
+ fs_1.default.writeFileSync(path_1.default.join(dir, '.awm', 'context', 'index.json'), JSON.stringify({
93
+ schema: 1,
94
+ kernelFiles: ['AGENTS.md', 'CONSTITUTION.md'],
95
+ maxFixedBytes: 33_740,
96
+ entries: [
97
+ { id: 'CTX-PROCESS-001', tier: 'kernel', path: 'CONSTITUTION.md', anchor: 'awm-context:CTX-PROCESS-001', when: 'always' },
98
+ { id: 'CTX-RELEASE-001', tier: 'selective', path: 'docs/awm/context/releases.md', anchor: 'awm-context:CTX-RELEASE-001', when: 'release automation' },
99
+ ],
100
+ }));
101
+ }
102
+ function writePartialKernel(dir) {
103
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'AGENTS.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\nagent rules\n');
104
+ }
54
105
  const check = (r, id) => r.checks.find(c => c.id === id);
55
106
  describe('preflight', () => {
56
- afterEach(() => mockRunSensors.mockReset());
107
+ beforeEach(() => {
108
+ mockCheckCurrentness.mockResolvedValue({
109
+ checkedAt: '2026-08-26T00:00:00.000Z',
110
+ compatibility: { status: 'not-checked' },
111
+ components: [{
112
+ component: 'cli', installed: '9.3.0', latest: '9.3.0', channel: 'stable',
113
+ source: 'https://registry.npmjs.org/agentic-workflow-manager',
114
+ checkedAt: '2026-08-26T00:00:00.000Z', status: 'current',
115
+ detail: 'Installed version matches npm dist-tags.latest.', remedy: 'No action required.',
116
+ }],
117
+ });
118
+ });
119
+ afterEach(() => {
120
+ mockRunSensors.mockReset();
121
+ mockCheckCurrentness.mockReset();
122
+ });
123
+ it('does not add a row when no registry declares the schema', async () => {
124
+ installRegistry({ minCliVersion: '9.2.1' });
125
+ const report = await (0, checks_1.preflight)(make({
126
+ manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
127
+ }));
128
+ expect(report.checks.map(c => c.id)).not.toContain('context-kernel');
129
+ });
130
+ it('blocks preflight when an active registry declares an invalid context kernel schema', async () => {
131
+ installRegistry({ projectContextSchema: 2 });
132
+ const report = await (0, checks_1.preflight)(make({
133
+ manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
134
+ }));
135
+ expect(report.status).toBe('degraded');
136
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
137
+ expect(check(report, 'context-kernel')).toMatchObject({
138
+ ok: false,
139
+ advisory: false,
140
+ detail: expect.stringMatching(/projectContextSchema.*exactly 1.*2/),
141
+ });
142
+ });
143
+ it('fails closed when an earlier active registry manifest is malformed before a valid declaration', async () => {
144
+ installRegistry({ projectContextSchema: 2 }, 'malformed');
145
+ installRegistry({ projectContextSchema: 1 }, 'valid');
146
+ const report = await (0, checks_1.preflight)(make({
147
+ manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
148
+ }));
149
+ expect(report.status).toBe('degraded');
150
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
151
+ expect(check(report, 'context-kernel')).toMatchObject({
152
+ ok: false,
153
+ advisory: false,
154
+ detail: expect.stringMatching(/malformed.*awm-registry\.json.*projectContextSchema.*exactly 1/i),
155
+ });
156
+ });
157
+ it('keeps legacy ready but renders a persistent warning and remedy', async () => {
158
+ installRegistry({ projectContextSchema: 1 });
159
+ const report = await (0, checks_1.preflight)(make({
160
+ manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
161
+ }));
162
+ expect(report.status).toBe('ready');
163
+ expect(report.checks).toContainEqual(expect.objectContaining({
164
+ id: 'context-kernel', ok: false, advisory: true,
165
+ detail: expect.stringMatching(/legacy full context/),
166
+ remedy: expect.stringMatching(/project-context-init/),
167
+ }));
168
+ expect((0, preflight_1.formatReport)(report)).toMatch(/⚠.*context-kernel.*legacy full context/s);
169
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(0);
170
+ expect(JSON.parse(JSON.stringify(report))).toEqual(expect.objectContaining({
171
+ status: 'ready',
172
+ checks: expect.arrayContaining([expect.objectContaining({
173
+ id: 'context-kernel', ok: false, advisory: true,
174
+ detail: expect.any(String), remedy: expect.any(String),
175
+ })]),
176
+ }));
177
+ });
178
+ it('keeps an unconfigured legacy project not_configured while the context kernel remains advisory', async () => {
179
+ installRegistry({ projectContextSchema: 1 });
180
+ const report = await (0, checks_1.preflight)(make());
181
+ expect(report.status).toBe('not_configured');
182
+ expect(check(report, 'manifest')).toMatchObject({ ok: false, detail: 'no .awm/sensors.json' });
183
+ expect(check(report, 'context-kernel')).toMatchObject({
184
+ ok: false,
185
+ advisory: true,
186
+ detail: expect.stringMatching(/legacy full context/),
187
+ });
188
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
189
+ });
190
+ it('passes a valid kernel', async () => {
191
+ installRegistry({ projectContextSchema: 1 });
192
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
193
+ writeValidKernel(dir);
194
+ const report = await (0, checks_1.preflight)(dir);
195
+ expect(report).toEqual(expect.objectContaining({ status: 'ready' }));
196
+ expect(check(report, 'context-kernel')).toMatchObject({ ok: true });
197
+ expect(check(report, 'context-kernel')).not.toHaveProperty('advisory');
198
+ });
199
+ it('degrades partial or invalid migration', async () => {
200
+ installRegistry({ projectContextSchema: 1 });
201
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
202
+ writePartialKernel(dir);
203
+ const report = await (0, checks_1.preflight)(dir);
204
+ expect(report.status).toBe('degraded');
205
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
206
+ expect(report.checks).toContainEqual(expect.objectContaining({
207
+ id: 'context-kernel', advisory: false, ok: false,
208
+ }));
209
+ expect((0, preflight_1.formatReport)(report)).toMatch(/Harness degraded — it has blocking preflight failures\./);
210
+ expect((0, preflight_1.formatReport)(report)).not.toMatch(/declares sensors it cannot run/);
211
+ });
212
+ it('degrades an invalid kernel even when no sensor manifest exists', async () => {
213
+ installRegistry({ projectContextSchema: 1 });
214
+ const dir = make();
215
+ writePartialKernel(dir);
216
+ const report = await (0, checks_1.preflight)(dir);
217
+ expect(report.status).toBe('degraded');
218
+ expect(check(report, 'manifest')).toMatchObject({ ok: false, detail: 'no .awm/sensors.json' });
219
+ expect(check(report, 'context-kernel')).toMatchObject({ ok: false, advisory: false });
220
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
221
+ });
57
222
  it('keeps default preflight static and does not dispatch sensors', async () => {
58
223
  const dir = make({
59
224
  manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
60
225
  });
61
226
  await (0, checks_1.preflight)(dir);
62
227
  expect(mockRunSensors).not.toHaveBeenCalled();
228
+ expect(mockCheckCurrentness).not.toHaveBeenCalled();
229
+ });
230
+ it('adds authoritative currentness only when strict mode is explicitly requested', async () => {
231
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
232
+ const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
233
+ expect(mockCheckCurrentness).toHaveBeenCalledWith(dir);
234
+ expect(report).toMatchObject({
235
+ status: 'ready',
236
+ currentness: expect.objectContaining({
237
+ components: [expect.objectContaining({ component: 'cli', status: 'current' })],
238
+ }),
239
+ });
240
+ expect(report.checks).toContainEqual(expect.objectContaining({ id: 'currentness', ok: true }));
241
+ expect((0, preflight_1.formatReport)(report)).toContain('Currentness: current');
242
+ expect(JSON.parse(JSON.stringify(report))).toHaveProperty('currentness.components');
243
+ });
244
+ it('returns a complete strict report instead of throwing for a malformed local registry inventory', async () => {
245
+ fs_1.default.writeFileSync(path_1.default.join(process.env.AWM_HOME, 'registries.json'), '{not-json');
246
+ mockCheckCurrentness.mockResolvedValue({
247
+ checkedAt: '2026-08-26T00:00:00.000Z', compatibility: { status: 'not-checked' },
248
+ components: [{
249
+ component: 'registry:inventory', installed: null, latest: null, channel: 'stable',
250
+ source: '[configured registry inventory]', checkedAt: '2026-08-26T00:00:00.000Z', status: 'unverifiable',
251
+ detail: 'Authoritative currentness could not be verified.',
252
+ remedy: 'Repair the local registry inventory and rerun strict preflight.',
253
+ }],
254
+ });
255
+ const report = await (0, checks_1.preflight)(make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } }), { requireCurrent: true });
256
+ expect(report.status).toBe('degraded');
257
+ expect(report.currentness?.components).toEqual(expect.arrayContaining([
258
+ expect.objectContaining({ component: 'registry:inventory', status: 'unverifiable', remedy: expect.stringMatching(/Repair the local registry inventory/) }),
259
+ ]));
260
+ expect(JSON.parse(JSON.stringify(report))).toHaveProperty('currentness.components');
261
+ });
262
+ it.each(['stale', 'pinned-behind', 'unverifiable'])('fails strict preflight for a %s component with its exact remedy', async (status) => {
263
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
264
+ mockCheckCurrentness.mockResolvedValue({
265
+ checkedAt: '2026-08-26T00:00:00.000Z', compatibility: { status: 'not-checked' },
266
+ components: [{
267
+ component: 'registry:baseline', installed: 'v1.0.0', latest: 'v1.1.0', channel: 'stable',
268
+ source: 'https://example.test/baseline.git', checkedAt: '2026-08-26T00:00:00.000Z', status,
269
+ detail: 'Authoritative currentness failed.',
270
+ remedy: status === 'pinned-behind' ? 'awm unpin baseline && awm update --yes' : 'awm update --yes',
271
+ }],
272
+ });
273
+ const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
274
+ expect(report.status).toBe('degraded');
275
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
276
+ expect(report.checks).toContainEqual(expect.objectContaining({ id: 'currentness', ok: false }));
277
+ expect((0, preflight_1.formatReport)(report)).toContain(status === 'pinned-behind' ? 'awm unpin baseline && awm update --yes' : 'awm update --yes');
278
+ });
279
+ it('keeps compatibility failure distinct when authoritative currentness is current', async () => {
280
+ installRegistry({ minCliVersion: '999.0.0' }, 'requires-new-cli');
281
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
282
+ const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
283
+ expect(report.status).toBe('degraded');
284
+ expect(report.checks).toEqual(expect.arrayContaining([
285
+ expect.objectContaining({ id: 'compatibility', ok: false, detail: expect.stringContaining('requires-new-cli requires CLI >= 999.0.0') }),
286
+ expect.objectContaining({ id: 'currentness', ok: true }),
287
+ ]));
288
+ });
289
+ it('retains both empirical sensor and currentness verdicts when both gates are requested', async () => {
290
+ const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
291
+ mockRunSensors.mockResolvedValue({ overall: 'pass', sensors: [] });
292
+ const report = await (0, checks_1.preflight)(dir, { verifySensors: true, requireCurrent: true });
293
+ expect(mockRunSensors).toHaveBeenCalledWith({ cwd: dir, all: true });
294
+ expect(mockCheckCurrentness).toHaveBeenCalledWith(dir);
295
+ expect(report.checks).toEqual(expect.arrayContaining([
296
+ expect.objectContaining({ id: 'sensors-execution', ok: true }),
297
+ expect.objectContaining({ id: 'currentness', ok: true }),
298
+ ]));
63
299
  });
64
300
  it('rejects an array passed as public preflight options before filesystem work', async () => {
65
301
  await expect((0, checks_1.preflight)(process.cwd(), []))
66
- .rejects.toThrow('preflight options must contain an optional boolean verifySensors');
302
+ .rejects.toThrow('preflight options must contain optional boolean verifySensors and requireCurrent');
67
303
  });
68
304
  it('requires an empirical sensor pass when verification is requested', async () => {
69
305
  const dir = make({
@@ -0,0 +1,144 @@
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
+ describe('Context Kernel v1 inspection', () => {
10
+ let root;
11
+ let previousAwmHome;
12
+ beforeEach(() => {
13
+ jest.resetModules();
14
+ previousAwmHome = process.env.AWM_HOME;
15
+ root = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-context-kernel-')));
16
+ process.env.AWM_HOME = path_1.default.join(root, 'awm-home');
17
+ fs_1.default.mkdirSync(process.env.AWM_HOME, { recursive: true });
18
+ });
19
+ afterEach(() => {
20
+ if (previousAwmHome === undefined)
21
+ delete process.env.AWM_HOME;
22
+ else
23
+ process.env.AWM_HOME = previousAwmHome;
24
+ fs_1.default.rmSync(root, { recursive: true, force: true });
25
+ });
26
+ function inspect() {
27
+ return require('../../../src/core/context-kernel/inspect').inspectContextKernel;
28
+ }
29
+ function indexPath() {
30
+ return path_1.default.join(root, '.awm', 'context', 'index.json');
31
+ }
32
+ function writeIndex(index) {
33
+ fs_1.default.mkdirSync(path_1.default.dirname(indexPath()), { recursive: true });
34
+ fs_1.default.writeFileSync(indexPath(), JSON.stringify(index, null, 2));
35
+ }
36
+ function writeValidKernel() {
37
+ const index = {
38
+ schema: 1,
39
+ kernelFiles: ['AGENTS.md', 'CONSTITUTION.md'],
40
+ maxFixedBytes: 33_740,
41
+ entries: [
42
+ { id: 'CTX-PROCESS-001', tier: 'kernel', path: 'CONSTITUTION.md', anchor: 'awm-context:CTX-PROCESS-001', when: 'always' },
43
+ { id: 'CTX-RELEASE-001', tier: 'selective', path: 'docs/awm/context/releases.md', anchor: 'awm-context:CTX-RELEASE-001', when: 'release automation' },
44
+ ],
45
+ };
46
+ fs_1.default.writeFileSync(path_1.default.join(root, 'AGENTS.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\nagent rules\n<!-- AWM:CONTEXT-KERNEL:END v1 -->\n');
47
+ fs_1.default.writeFileSync(path_1.default.join(root, 'CONSTITUTION.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\n<!-- awm-context:CTX-PROCESS-001 -->\nprocess rules\n<!-- AWM:CONTEXT-KERNEL:END v1 -->\n');
48
+ const card = path_1.default.join(root, 'docs', 'awm', 'context', 'releases.md');
49
+ fs_1.default.mkdirSync(path_1.default.dirname(card), { recursive: true });
50
+ fs_1.default.writeFileSync(card, '<!-- awm-context:CTX-RELEASE-001 -->\nrelease rules\n');
51
+ writeIndex(index);
52
+ return index;
53
+ }
54
+ function mutateFixture(mutation) {
55
+ const constitution = path_1.default.join(root, 'CONSTITUTION.md');
56
+ if (mutation === 'start marker missing') {
57
+ fs_1.default.writeFileSync(constitution, fs_1.default.readFileSync(constitution, 'utf8').replace('<!-- AWM:CONTEXT-KERNEL:START v1 -->\n', ''));
58
+ }
59
+ else if (mutation === 'end marker duplicated') {
60
+ fs_1.default.appendFileSync(constitution, '<!-- AWM:CONTEXT-KERNEL:END v1 -->\n');
61
+ }
62
+ else if (mutation === 'anchor outside region') {
63
+ fs_1.default.writeFileSync(constitution, '<!-- awm-context:CTX-PROCESS-001 -->\n' + fs_1.default.readFileSync(constitution, 'utf8'));
64
+ }
65
+ else {
66
+ fs_1.default.writeFileSync(constitution, fs_1.default.readFileSync(constitution, 'utf8').replace('process rules', '<!-- awm-context:CTX-PROCESS-001 -->\nprocess rules'));
67
+ }
68
+ }
69
+ it('classifies no R3 artifacts as legacy', () => {
70
+ expect(inspect()(root)).toEqual({ state: 'legacy' });
71
+ });
72
+ it('rejects a root context marker when the Context Kernel index is missing', () => {
73
+ fs_1.default.writeFileSync(path_1.default.join(root, 'AGENTS.md'), '<!-- AWM:CONTEXT-KERNEL:START v1 -->\nagent rules\n');
74
+ expect(inspect()(root)).toEqual(expect.objectContaining({
75
+ state: 'invalid',
76
+ detail: expect.stringContaining('.awm/context/index.json is missing'),
77
+ }));
78
+ });
79
+ it('rejects an orphaned context anchor when the Context Kernel index is missing', () => {
80
+ fs_1.default.writeFileSync(path_1.default.join(root, 'AGENTS.md'), '<!-- awm-context:CTX-PROCESS-001 -->\nagent rules\n');
81
+ expect(inspect()(root)).toEqual(expect.objectContaining({
82
+ state: 'invalid',
83
+ detail: expect.stringContaining('.awm/context/index.json is missing'),
84
+ }));
85
+ });
86
+ it('accepts the canonical fixture', () => {
87
+ writeValidKernel();
88
+ expect(inspect()(root)).toEqual(expect.objectContaining({ state: 'valid', schema: 1 }));
89
+ });
90
+ it.each([
91
+ ['unknown top-level field', (x) => { x.extra = true; }],
92
+ ['future schema', (x) => { x.schema = 2; }],
93
+ ['duplicate id', (x) => { x.entries[1].id = x.entries[0].id; }],
94
+ ['duplicate anchor', (x) => { x.entries[1].anchor = x.entries[0].anchor; }],
95
+ ['absolute path', (x) => { x.entries[1].path = '/tmp/outside.md'; }],
96
+ ['traversal', (x) => { x.entries[1].path = '../outside.md'; }],
97
+ ['empty when', (x) => { x.entries[1].when = ''; }],
98
+ ['fixed byte cap', (x) => { x.maxFixedBytes = 1; }],
99
+ ])('rejects %s', (_name, mutate) => {
100
+ const index = writeValidKernel();
101
+ mutate(index);
102
+ writeIndex(index);
103
+ expect(inspect()(root)).toEqual(expect.objectContaining({ state: 'invalid' }));
104
+ });
105
+ it('rejects a project path that resolves through a symlink to an external regular file', () => {
106
+ const index = writeValidKernel();
107
+ const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-outside-'));
108
+ try {
109
+ const card = path_1.default.join(root, index.entries[1].path);
110
+ const externalCard = path_1.default.join(outside, 'card.md');
111
+ fs_1.default.writeFileSync(externalCard, '<!-- awm-context:CTX-RELEASE-001 -->\nexternal release rules\n');
112
+ fs_1.default.rmSync(card);
113
+ fs_1.default.symlinkSync(externalCard, card);
114
+ expect(fs_1.default.statSync(card).isFile()).toBe(true);
115
+ expect(fs_1.default.realpathSync(card)).toBe(externalCard);
116
+ expect(inspect()(root)).toEqual(expect.objectContaining({
117
+ state: 'invalid',
118
+ detail: expect.stringContaining('resolves outside the project root'),
119
+ }));
120
+ }
121
+ finally {
122
+ fs_1.default.rmSync(outside, { recursive: true, force: true });
123
+ }
124
+ });
125
+ it('rejects a dangling index symlink instead of treating it as legacy', () => {
126
+ const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-outside-'));
127
+ try {
128
+ fs_1.default.mkdirSync(path_1.default.dirname(indexPath()), { recursive: true });
129
+ fs_1.default.symlinkSync(path_1.default.join(outside, 'index.json'), indexPath());
130
+ expect(inspect()(root)).toEqual(expect.objectContaining({ state: 'invalid' }));
131
+ }
132
+ finally {
133
+ fs_1.default.rmSync(outside, { recursive: true, force: true });
134
+ }
135
+ });
136
+ it.each(['start marker missing', 'end marker duplicated', 'anchor outside region', 'anchor duplicated'])('rejects %s', (mutation) => {
137
+ writeValidKernel();
138
+ mutateFixture(mutation);
139
+ expect(inspect()(root)).toEqual(expect.objectContaining({ state: 'invalid' }));
140
+ });
141
+ it('throws for an invalid public cwd', () => {
142
+ expect(() => inspect()(path_1.default.join(root, 'missing'))).toThrow(/cwd.*directory/);
143
+ });
144
+ });