@aviaratech/ai-delivery 0.3.9 → 0.3.11

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/README.md CHANGED
@@ -10,6 +10,15 @@ Install the versioned package from npm or a reviewed package archive. The
10
10
  consuming repository must run its own configuration and runtime-admission
11
11
  procedure before any lifecycle write.
12
12
 
13
+ The selected CLI, MCP server and verification controller use Node 24.21.0 and
14
+ npm 11.19.0. The public library exports also support Node 26.2.0 consumers.
15
+ Package checks run the Node 24 controller against actual npm-packed Node 26
16
+ consumers, including filesystem fixture contention, reader and link rejection,
17
+ success/failure teardown, cancellation, leaked fixtures and positive byte limits.
18
+ For package development, set `AI_DELIVERY_NODE26_EXECUTABLE` to the absolute
19
+ Node 26.2.0 executable before `npm run checks`; CI retains that executable while
20
+ selecting Node 24 for the canonical commands.
21
+
13
22
  ```sh
14
23
  node --version # 24.21.0
15
24
  npm --version # 11.19.0
@@ -323,7 +332,7 @@ The classifier owns the digest values and must include every relevant source, co
323
332
 
324
333
  Verification holds one file lease per worktree and durably records its writer and observed command process identities. A second writer in the same worktree is rejected before execution. Separate worktrees can overlap, including at the same head, with separate stage inputs and mutable manifests. Callers must first qualify their commands' CPU, memory, disk, I/O and shared-file/service requirements; a resource class alone grants no shared-resource exclusivity. The runtime adds no global executor cap. On retry after writer interruption, it refuses live or unknown ownership and reconciles only the recorded command group and observed descendants before reusing compatible completed stages. A launch interrupted before command identity was recorded requires explicit reconciliation. Process identity observation is required on supported POSIX hosts; unavailable or ambiguous observation fails closed.
325
334
 
326
- The `--admit` flag explicitly admits additional resource classes. Publication creates a draft PR only after exact-head verification; a high-risk policy also requires a prepublication review artifact. Ready promotion and merge require a submitted formal review. Merge checks live blockers, checks, base/head coordinates, and the policy boundary. Repositories may require an exact base/head lease for merge.
335
+ The `--admit` flag explicitly admits additional resource classes for execution. Compatible complete stages are reused only after their existing stage, artifact, command-output and applicable resource proofs are fully validated, without admitting execution of those classes. Missing or incompatible proof requires the current invocation's admission for the actual stage class before any command runs; corrupt proof remains a hard failure. This permits serial `source_only`, `model`, then `postgres_docker` invocations to reuse prior completed stages while admitting only the next class to execute. Publication creates a draft PR only after exact-head verification; a high-risk policy also requires a prepublication review artifact. Ready promotion and merge require a submitted formal review. Merge checks live blockers, checks, base/head coordinates, and the policy boundary. Repositories may require an exact base/head lease for merge.
327
336
 
328
337
  For a bounded run, pass `--max-aggregate-rss-bytes`, `--min-free-disk-bytes`, and optionally `--max-new-output-bytes` with one or more `--output-root` paths to `verify`. `issue_verify` accepts the same values in `resourceBounds`. RSS is sampled across the observed child process tree, including observed detached descendants; free disk is checked before commands and during execution on the worktree and declared output filesystems. Positive file-size growth is sampled under the declared roots, with previously measured completed stages carried across a resume. The caller must declare every output root relevant to its allowance; aliases must resolve within those roots. The roots are relative to the issue worktree unless absolute. Output growth is separate from the 8 MiB captured stdout/stderr limit. Resource limits are opt in and do not impose a total runtime deadline.
329
338
 
@@ -4,7 +4,8 @@ import { createHash } from 'node:crypto';
4
4
  import { cpSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, utimesSync, writeFileSync, } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { dirname, join } from 'node:path';
7
- import { test, vi } from 'vitest';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { afterAll, test, vi } from 'vitest';
8
9
  import { syntheticDiscoveryClients, syntheticOverrides } from './fixtures/discovery.js';
9
10
  import { Octokit } from '@octokit/rest';
10
11
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -12,7 +13,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
12
13
  import { AI_DELIVERY_MCP_TOOLS } from './mcp/tools.js';
13
14
  import { createAiDeliveryMcpServer } from './mcp/index.js';
14
15
  import { loadDeliveryConfig, parseDeliveryConfig } from './config/deliveryConfig.js';
15
- import { digestValue } from './delivery/index.js';
16
+ import { digestValue, stableJson } from './delivery/index.js';
16
17
  import { contextFor, executeTool, resumedIssueUpdate, startTrackedIssue } from './dispatch.js';
17
18
  import { defaultBaseRef, gitCommonDir } from './git.js';
18
19
  import * as githubClient from './github/client.js';
@@ -210,6 +211,7 @@ async function fixture(options = {}) {
210
211
  const remoteName = options.remote ?? 'origin';
211
212
  const counter = join(root, '.git', 'stage-count.txt');
212
213
  const secondCounter = join(root, '.git', 'second-stage-count.txt');
214
+ const thirdCounter = join(root, '.git', 'third-stage-count.txt');
213
215
  const failSecond = join(root, '.git', 'fail-second-stage');
214
216
  try {
215
217
  git(root, 'init', '-q', '-b', 'main');
@@ -290,11 +292,16 @@ export default {
290
292
  requiredStages: [{ id: 'check', dependsOn: [], semanticInputKeys: ['source'],
291
293
  ${options.componentPolicy ? "semanticInputs: [{ key: 'source', digest: bytes(readFileSync(new URL('./artifact.txt', import.meta.url))) }]," : ''}
292
294
  resourceClass: 'source_only', commands: [{ label: 'count', argv: [process.execPath, '-e',
293
- ${JSON.stringify(options.firstStageScript ?? `const fs=require('fs');const p=${JSON.stringify(counter)};fs.writeFileSync(p,String(Number(fs.existsSync(p)?fs.readFileSync(p,'utf8'):0)+1));`)}] }] }${options.twoStages
295
+ ${JSON.stringify(options.firstStageScript ?? `const fs=require('fs');const p=${JSON.stringify(counter)};fs.writeFileSync(p,String(Number(fs.existsSync(p)?fs.readFileSync(p,'utf8'):0)+1));`)}] }] }${options.twoStages || options.serialResourceStages
294
296
  ? `, { id: 'second', dependsOn: ['check'], semanticInputKeys: ['source'],
295
297
  ${options.componentPolicy ? "semanticInputs: [{ key: 'source', digest: bytes(readFileSync(new URL('./component.txt', import.meta.url))) }]," : ''}
296
- resourceClass: 'source_only', commands: [{ label: 'retry', argv: [process.execPath, '-e',
298
+ resourceClass: '${options.serialResourceStages ? 'model' : 'source_only'}', commands: [{ label: 'retry', argv: [process.execPath, '-e',
297
299
  ${JSON.stringify(options.secondStageScript ?? `const fs=require('fs');const p=${JSON.stringify(secondCounter)};fs.writeFileSync(p,String(Number(fs.existsSync(p)?fs.readFileSync(p,'utf8'):0)+1));if(fs.existsSync(${JSON.stringify(failSecond)}))process.exit(7);`)}] }] }`
300
+ : ''}${options.serialResourceStages
301
+ ? `, { id: 'postgres', dependsOn: ['second'], semanticInputKeys: ['source'],
302
+ ${options.componentPolicy ? "semanticInputs: [{ key: 'source', digest: bytes(readFileSync(new URL('./component.txt', import.meta.url))) }]," : ''}
303
+ resourceClass: 'postgres_docker', commands: [{ label: 'count', argv: [process.execPath, '-e',
304
+ ${JSON.stringify(`const fs=require('fs');const p=${JSON.stringify(thirdCounter)};fs.writeFileSync(p,String(Number(fs.existsSync(p)?fs.readFileSync(p,'utf8'):0)+1));`)}] }] }`
298
305
  : ''}],
299
306
  risk: 'standard' };
300
307
  },
@@ -353,7 +360,7 @@ export default {
353
360
  mkdirSync(dirname(admissionPath), { recursive: true });
354
361
  if (!options.omitRuntimeAdmission)
355
362
  writeFileSync(admissionPath, JSON.stringify({ ...content, admissionId: digestValue(content) }), { mode: 0o600 });
356
- return { root, counter, secondCounter, failSecond, runtimeEntryPath };
363
+ return { root, counter, secondCounter, thirdCounter, failSecond, runtimeEntryPath };
357
364
  }
358
365
  catch (error) {
359
366
  rmSync(root, { recursive: true, force: true });
@@ -1803,6 +1810,224 @@ test('a bounded passing run records measured resources in versioned evidence', a
1803
1810
  rmSync(root, { recursive: true, force: true });
1804
1811
  }
1805
1812
  });
1813
+ test.each([
1814
+ { bounded: false, componentPolicy: false },
1815
+ { bounded: true, componentPolicy: false },
1816
+ { bounded: true, componentPolicy: true },
1817
+ ])('serial admission reuses complete stages with bounds $bounded and component policy $componentPolicy', async ({ bounded, componentPolicy }) => {
1818
+ const { root, counter, secondCounter, thirdCounter } = await fixture({
1819
+ serialResourceStages: true,
1820
+ componentPolicy,
1821
+ });
1822
+ try {
1823
+ const row = await prepareIssueWorktree({
1824
+ baseRef: 'main',
1825
+ identity: 'synthetic-author',
1826
+ issueNumber: 17,
1827
+ repoRoot: root,
1828
+ });
1829
+ const input = {
1830
+ issueNumber: 17,
1831
+ repoRoot: row.path,
1832
+ ...(bounded ? { resourceBounds: { maxAggregateRssBytes: 1_000_000_000, minFreeDiskBytes: 1 } } : {}),
1833
+ };
1834
+ await assert.rejects(verifyIssue({ ...input, admittedResourceClasses: ['source_only'] }), /Stage 'second' requires explicit model admission/u);
1835
+ assert.equal(readFileSync(counter, 'utf8'), '1');
1836
+ assert.equal(existsSync(secondCounter), false);
1837
+ assert.equal(existsSync(thirdCounter), false);
1838
+ await assert.rejects(verifyIssue({ ...input, admittedResourceClasses: ['model'] }), /Stage 'postgres' requires explicit postgres_docker admission/u);
1839
+ assert.equal(readFileSync(counter, 'utf8'), '1');
1840
+ assert.equal(readFileSync(secondCounter, 'utf8'), '1');
1841
+ assert.equal(existsSync(thirdCounter), false);
1842
+ const completed = await verifyIssue({ ...input, admittedResourceClasses: ['postgres_docker'] });
1843
+ const repeated = await verifyIssue({ ...input, admittedResourceClasses: [] });
1844
+ assert.deepEqual(repeated.stageReceipts, completed.stageReceipts);
1845
+ for (const path of [counter, secondCounter, thirdCounter])
1846
+ assert.equal(readFileSync(path, 'utf8'), '1');
1847
+ if (bounded) {
1848
+ assert.deepEqual(repeated.resources, completed.resources);
1849
+ assert.ok((repeated.resources?.sampleCount ?? 0) > 0);
1850
+ const first = completed.stageReceipts[0];
1851
+ const stagePath = join(root, '.git', 'ai-delivery', first.input.schemaVersion === 'ai-delivery.stage-input@2' ? 'verification@2' : 'verification@1', 'stages', 'check', `${first.input.inputId.slice(7)}.json`);
1852
+ rmSync(stagePath);
1853
+ const repaired = await verifyIssue({ ...input, admittedResourceClasses: [] });
1854
+ assert.deepEqual(repaired.stageReceipts, completed.stageReceipts);
1855
+ assert.deepEqual(repaired.resources, completed.resources);
1856
+ assert.equal(existsSync(stagePath), true);
1857
+ assert.equal(readFileSync(counter, 'utf8'), '1');
1858
+ }
1859
+ }
1860
+ finally {
1861
+ rmSync(root, { recursive: true, force: true });
1862
+ }
1863
+ });
1864
+ test.each(['missing', 'unmeasured', 'source', 'command', 'environment', 'runtime', 'producer', 'bounds'])('serial admission refuses execution after %s proof invalidation', async (changed) => {
1865
+ const { root, counter, secondCounter, thirdCounter } = await fixture({ serialResourceStages: true });
1866
+ const previousPath = process.env.PATH;
1867
+ const versionDescriptor = Object.getOwnPropertyDescriptor(process, 'version');
1868
+ const producerPath = join(import.meta.dirname, 'serial-producer-fixture.js');
1869
+ let producerOwned = false;
1870
+ try {
1871
+ const row = await prepareIssueWorktree({
1872
+ baseRef: 'main',
1873
+ identity: 'synthetic-author',
1874
+ issueNumber: 17,
1875
+ repoRoot: root,
1876
+ });
1877
+ const input = {
1878
+ issueNumber: 17,
1879
+ repoRoot: row.path,
1880
+ resourceBounds: { maxAggregateRssBytes: 1_000_000_000, minFreeDiskBytes: 1 },
1881
+ };
1882
+ const completed = await verifyIssue({
1883
+ ...(changed === 'unmeasured' ? { issueNumber: input.issueNumber, repoRoot: input.repoRoot } : input),
1884
+ admittedResourceClasses: ['source_only', 'model', 'postgres_docker'],
1885
+ });
1886
+ if (changed === 'missing') {
1887
+ const id = completed.stageReceipts[0].input.inputId.slice(7);
1888
+ rmSync(join(root, '.git', 'ai-delivery', 'verification@1', 'stages', 'check', `${id}.json`));
1889
+ rmSync(join(root, '.git', 'ai-delivery', 'resource-stages@1', `${id}.json`));
1890
+ }
1891
+ else if (changed === 'source' || changed === 'command') {
1892
+ const path = join(row.path, changed === 'source' ? 'change.txt' : 'policy.mjs');
1893
+ writeFileSync(path, changed === 'source'
1894
+ ? 'changed source\n'
1895
+ : readFileSync(path, 'utf8').replace("label: 'count'", "label: 'changed-count'"));
1896
+ git(row.path, 'add', '.');
1897
+ git(row.path, 'commit', '-qm', 'synthetic checkpoint invalidation');
1898
+ }
1899
+ else if (changed === 'environment')
1900
+ process.env.PATH = `${previousPath ?? ''}:/synthetic-environment`;
1901
+ else if (changed === 'runtime')
1902
+ Object.defineProperty(process, 'version', { value: 'v0.0.0' });
1903
+ else if (changed === 'producer') {
1904
+ assert.equal(existsSync(producerPath), false);
1905
+ writeFileSync(producerPath, '// changed synthetic runner identity\n');
1906
+ producerOwned = true;
1907
+ }
1908
+ else if (changed === 'bounds')
1909
+ input.resourceBounds.maxAggregateRssBytes -= 1;
1910
+ await assert.rejects(verifyIssue({ ...input, admittedResourceClasses: ['postgres_docker'] }), /Stage 'check' requires explicit source_only admission/u);
1911
+ for (const path of [counter, secondCounter, thirdCounter])
1912
+ assert.equal(readFileSync(path, 'utf8'), '1');
1913
+ }
1914
+ finally {
1915
+ if (previousPath === undefined)
1916
+ delete process.env.PATH;
1917
+ else
1918
+ process.env.PATH = previousPath;
1919
+ Object.defineProperty(process, 'version', versionDescriptor);
1920
+ if (producerOwned)
1921
+ rmSync(producerPath);
1922
+ rmSync(root, { recursive: true, force: true });
1923
+ }
1924
+ });
1925
+ test.each([
1926
+ 'stage',
1927
+ 'canonical',
1928
+ 'artifact',
1929
+ 'output',
1930
+ 'resource',
1931
+ 'resource-bounds',
1932
+ 'resource-samples',
1933
+ 'missing-resource',
1934
+ 'baseline',
1935
+ ])('serial admission rejects corrupt %s proof before considering execution', async (corrupt) => {
1936
+ const { root, counter, secondCounter, thirdCounter } = await fixture({ serialResourceStages: true });
1937
+ try {
1938
+ const row = await prepareIssueWorktree({
1939
+ baseRef: 'main',
1940
+ identity: 'synthetic-author',
1941
+ issueNumber: 17,
1942
+ repoRoot: root,
1943
+ });
1944
+ mkdirSync(join(row.path, '.issue-cli'), { recursive: true });
1945
+ const input = {
1946
+ issueNumber: 17,
1947
+ repoRoot: row.path,
1948
+ resourceBounds: {
1949
+ maxAggregateRssBytes: 1_000_000_000,
1950
+ minFreeDiskBytes: 1,
1951
+ maxNewOutputBytes: 1_000_000,
1952
+ outputRoots: ['.issue-cli'],
1953
+ },
1954
+ };
1955
+ const completed = await verifyIssue({
1956
+ ...input,
1957
+ admittedResourceClasses: ['source_only', 'model', 'postgres_docker'],
1958
+ });
1959
+ const first = completed.stageReceipts[0];
1960
+ const common = join(root, '.git', 'ai-delivery');
1961
+ const id = first.input.inputId.slice(7);
1962
+ const stagePath = join(common, 'verification@1', 'stages', 'check', `${id}.json`);
1963
+ const resourcePath = join(common, 'resource-stages@1', `${id}.json`);
1964
+ if (corrupt === 'stage')
1965
+ writeFileSync(stagePath, 'corrupt stage proof\n');
1966
+ else if (corrupt === 'canonical')
1967
+ writeFileSync(stagePath, `${readFileSync(stagePath, 'utf8')}\n`);
1968
+ else if (corrupt === 'artifact') {
1969
+ const { receiptId: _receiptId, ...content } = first;
1970
+ const altered = { ...content, artifacts: [{ path: 'artifact.txt', digest: digestValue('corrupt artifact') }] };
1971
+ writeFileSync(stagePath, stableJson({ ...altered, receiptId: digestValue(altered) }));
1972
+ }
1973
+ else if (corrupt === 'output') {
1974
+ writeFileSync(join(common, 'verification@1', 'command-output', `${first.commands[0].outputDigest.slice(7)}.bin`), 'corrupt output');
1975
+ }
1976
+ else if (corrupt === 'resource')
1977
+ writeFileSync(resourcePath, 'corrupt resource proof\n');
1978
+ else if (corrupt === 'missing-resource')
1979
+ rmSync(resourcePath);
1980
+ else if (corrupt === 'baseline') {
1981
+ const directory = join(common, 'output-baselines@1');
1982
+ const file = readdirSync(directory).find((name) => name.endsWith('.json') && !name.endsWith('.state.json'));
1983
+ writeFileSync(join(directory, file), 'corrupt baseline proof\n');
1984
+ }
1985
+ else {
1986
+ const { checkpointId: _checkpointId, ...content } = JSON.parse(readFileSync(resourcePath, 'utf8'));
1987
+ if (corrupt === 'resource-bounds')
1988
+ content.resources.bounds.maxAggregateRssBytes += 1;
1989
+ else
1990
+ content.resources.sampleCount = 0;
1991
+ writeFileSync(resourcePath, JSON.stringify({ ...content, checkpointId: digestValue(content) }));
1992
+ }
1993
+ await assert.rejects(verifyIssue({ ...input, admittedResourceClasses: ['postgres_docker'] }), (error) => error instanceof Error && !error.message.includes('admission'));
1994
+ for (const path of [counter, secondCounter, thirdCounter])
1995
+ assert.equal(readFileSync(path, 'utf8'), '1');
1996
+ }
1997
+ finally {
1998
+ rmSync(root, { recursive: true, force: true });
1999
+ }
2000
+ });
2001
+ test('serial admission requires dependent execution after a real upstream rerun', async () => {
2002
+ const { root, counter, secondCounter, thirdCounter } = await fixture({ serialResourceStages: true });
2003
+ try {
2004
+ const row = await prepareIssueWorktree({
2005
+ baseRef: 'main',
2006
+ identity: 'synthetic-author',
2007
+ issueNumber: 17,
2008
+ repoRoot: root,
2009
+ });
2010
+ const input = {
2011
+ issueNumber: 17,
2012
+ repoRoot: row.path,
2013
+ resourceBounds: { maxAggregateRssBytes: 1_000_000_000, minFreeDiskBytes: 1 },
2014
+ };
2015
+ const completed = await verifyIssue({
2016
+ ...input,
2017
+ admittedResourceClasses: ['source_only', 'model', 'postgres_docker'],
2018
+ });
2019
+ const id = completed.stageReceipts[0].input.inputId.slice(7);
2020
+ rmSync(join(root, '.git', 'ai-delivery', 'verification@1', 'stages', 'check', `${id}.json`));
2021
+ rmSync(join(root, '.git', 'ai-delivery', 'resource-stages@1', `${id}.json`));
2022
+ await assert.rejects(verifyIssue({ ...input, admittedResourceClasses: ['source_only'] }), /Stage 'second' requires explicit model admission/u);
2023
+ assert.equal(readFileSync(counter, 'utf8'), '2');
2024
+ assert.equal(readFileSync(secondCounter, 'utf8'), '1');
2025
+ assert.equal(readFileSync(thirdCounter, 'utf8'), '1');
2026
+ }
2027
+ finally {
2028
+ rmSync(root, { recursive: true, force: true });
2029
+ }
2030
+ });
1806
2031
  test('bounded verification reruns unmeasured work and reuses only matching measured stages', async () => {
1807
2032
  const { root, counter, secondCounter, failSecond } = await fixture({ twoStages: true });
1808
2033
  try {
@@ -4027,13 +4252,71 @@ test('fully cached bounded verification still rejects a newly leaked FIFO', asyn
4027
4252
  rmSync(root, { recursive: true, force: true });
4028
4253
  }
4029
4254
  });
4030
- function filesystemFixtureScript(output, mode) {
4255
+ let node26Package;
4256
+ function packedNode26Consumer() {
4257
+ if (node26Package !== undefined)
4258
+ return node26Package;
4259
+ assert.equal(process.version, 'v24.21.0', 'canonical verification controller must remain Node 24.21.0');
4260
+ const executable = process.env['AI_DELIVERY_NODE26_EXECUTABLE'];
4261
+ assert.ok(executable, 'set AI_DELIVERY_NODE26_EXECUTABLE to the actual Node 26.2.0 executable');
4262
+ assert.equal(execFileSync(executable, ['--version'], { encoding: 'utf8' }).trim(), 'v26.2.0');
4263
+ const temporary = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-node26-')));
4264
+ try {
4265
+ const source = fileURLToPath(new URL('../', import.meta.url));
4266
+ const pack = JSON.parse(execFileSync('npm', ['pack', '--json', '--pack-destination', temporary], {
4267
+ cwd: source,
4268
+ encoding: 'utf8',
4269
+ maxBuffer: 4 * 1024 * 1024,
4270
+ }));
4271
+ execFileSync('tar', ['-xzf', join(temporary, pack[0].filename), '-C', temporary]);
4272
+ const root = join(temporary, 'package');
4273
+ symlinkSync(join(source, 'node_modules'), join(root, 'node_modules'), 'dir');
4274
+ process.stderr.write(`ai-delivery.node26-consumer ${JSON.stringify({
4275
+ controller: process.version,
4276
+ consumer: 'v26.2.0',
4277
+ archiveSha256: createHash('sha256')
4278
+ .update(readFileSync(join(temporary, pack[0].filename)))
4279
+ .digest('hex'),
4280
+ manifestSha256: createHash('sha256')
4281
+ .update(readFileSync(join(root, 'package.json')))
4282
+ .digest('hex'),
4283
+ })}\n`);
4284
+ node26Package = { executable: realpathSync(executable), root, temporary };
4285
+ return node26Package;
4286
+ }
4287
+ catch (error) {
4288
+ rmSync(temporary, { recursive: true, force: true });
4289
+ throw error;
4290
+ }
4291
+ }
4292
+ afterAll(() => {
4293
+ if (node26Package !== undefined)
4294
+ rmSync(node26Package.temporary, { recursive: true, force: true });
4295
+ });
4296
+ function node26ConsumerScript(script) {
4297
+ const consumer = packedNode26Consumer();
4031
4298
  return `
4299
+ require('node:assert/strict').equal(process.version, 'v24.21.0');
4300
+ const child = require('node:child_process').spawnSync(${JSON.stringify(consumer.executable)}, ['-e', ${JSON.stringify(script)}], { cwd: ${JSON.stringify(consumer.root)}, stdio: 'inherit' });
4301
+ if (child.error) throw child.error;
4302
+ process.exit(child.status ?? 1);`;
4303
+ }
4304
+ function filesystemFixtureScript(output, mode, runtime = 'node24') {
4305
+ const script = `
4032
4306
  (async () => {
4033
4307
  const fs = require('node:fs'), cp = require('node:child_process'), path = require('node:path'), assert = require('node:assert/strict');
4034
- const { withVerificationFilesystemFixture } = await import(${JSON.stringify(new URL('./agent.js', import.meta.url).href)});
4035
- const { assertPrivateFile } = await import(${JSON.stringify(new URL('./delivery/common.js', import.meta.url).href)});
4308
+ assert.equal(process.version, ${JSON.stringify(runtime === 'node26' ? 'v26.2.0' : 'v24.21.0')});
4309
+ const { withVerificationFilesystemFixture } = await import(${JSON.stringify(runtime === 'node26' ? '@aviaratech/ai-delivery/agent' : new URL('./agent.js', import.meta.url).href)});
4310
+ const { loadValidRepositoryDeliveryEvidence } = await import(${JSON.stringify(runtime === 'node26' ? '@aviaratech/ai-delivery/delivery' : new URL('./delivery/index.js', import.meta.url).href)});
4036
4311
  const output = ${JSON.stringify(output)}, fifo = path.join(output, 'fifo'), events = path.join(output, 'events');
4312
+ const evidenceId = 'sha256:' + 'a'.repeat(64), currentHead = { sha: 'b'.repeat(40), tree: 'c'.repeat(40) };
4313
+ const evidence = path.join(output, 'ai-delivery', 'receipts', 'delivery@1', currentHead.sha, evidenceId.slice(7) + '.json');
4314
+ fs.mkdirSync(path.dirname(evidence), { recursive: true });
4315
+ const rejectPrivateFile = file => {
4316
+ fs.renameSync(file, evidence);
4317
+ try { assert.throws(() => loadValidRepositoryDeliveryEvidence({ createInput: { gitCommonDir: output, currentHead }, evidenceId }), /private regular file/); }
4318
+ finally { fs.renameSync(evidence, file); }
4319
+ };
4037
4320
  const gate = JSON.parse(process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE);
4038
4321
  const trace = value => fs.appendFileSync(events, value + '\\n');
4039
4322
  await withVerificationFilesystemFixture(async () => {
@@ -4046,20 +4329,26 @@ function filesystemFixtureScript(output, mode) {
4046
4329
  if (${JSON.stringify(mode)} === 'block') fs.readFileSync(fifo);
4047
4330
  while (fs.readdirSync(path.join(gate.directory, 'requests')).length === 0) await new Promise(resolve => setTimeout(resolve, 10));
4048
4331
  assert.ok(fs.existsSync(fifo)); trace('scan-queued');
4049
- assert.throws(() => assertPrivateFile(fifo), /private regular file/); trace('reader-rejected');
4332
+ rejectPrivateFile(fifo); trace('reader-rejected');
4050
4333
  const regular = path.join(output, 'regular'), hard = path.join(output, 'hard'), alias = path.join(output, 'alias');
4051
4334
  fs.writeFileSync(regular, 'x'.repeat(128), { mode: 0o600 });
4052
- fs.linkSync(regular, hard); assert.throws(() => assertPrivateFile(hard), /private regular file/); fs.unlinkSync(hard);
4053
- fs.symlinkSync('./regular', alias); assert.throws(() => assertPrivateFile(alias), /private regular file/); fs.unlinkSync(alias);
4335
+ fs.linkSync(regular, hard); rejectPrivateFile(hard); fs.unlinkSync(hard);
4336
+ fs.symlinkSync('./regular', alias); rejectPrivateFile(alias); fs.unlinkSync(alias);
4054
4337
  if (${JSON.stringify(mode)} === 'assertion') throw new Error('synthetic reader assertion failure');
4055
4338
  } finally { fs.unlinkSync(fifo); trace('cleaned'); }
4056
4339
  });
4057
4340
  trace('released');
4058
4341
  })().catch(error => { console.error(error.message); process.exitCode = 1; });`;
4342
+ return runtime === 'node26' ? node26ConsumerScript(script) : script;
4059
4343
  }
4060
- test.each(['pass', 'assertion'])('filesystem fixture gate deterministically queues a scan through reader rejection and %s teardown', async (mode) => {
4344
+ test.each([
4345
+ ['pass', 'node24'],
4346
+ ['assertion', 'node24'],
4347
+ ['pass', 'node26'],
4348
+ ['assertion', 'node26'],
4349
+ ])('filesystem fixture gate deterministically queues a scan through reader rejection and %s teardown on %s', async (mode, runtime) => {
4061
4350
  const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-gate-')));
4062
- const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
4351
+ const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
4063
4352
  try {
4064
4353
  const row = await prepareIssueWorktree({
4065
4354
  baseRef: 'main',
@@ -4095,10 +4384,17 @@ test.each(['pass', 'assertion'])('filesystem fixture gate deterministically queu
4095
4384
  rmSync(output, { recursive: true, force: true });
4096
4385
  }
4097
4386
  });
4098
- test.each(['abrupt', 'block', 'cancel'])('filesystem fixture gate preserves leaked FIFO and unrelated files after %s', async (mode) => {
4387
+ test.each([
4388
+ ['abrupt', 'node24'],
4389
+ ['block', 'node24'],
4390
+ ['cancel', 'node24'],
4391
+ ['abrupt', 'node26'],
4392
+ ['block', 'node26'],
4393
+ ['cancel', 'node26'],
4394
+ ])('filesystem fixture gate preserves leaked FIFO and unrelated files after %s on %s', async (mode, runtime) => {
4099
4395
  const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-failure-')));
4100
4396
  writeFileSync(join(output, 'unrelated'), 'preserved');
4101
- const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
4397
+ const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
4102
4398
  const controller = new AbortController();
4103
4399
  let running;
4104
4400
  const progress = vi.spyOn(process.stderr, 'write');
@@ -4150,9 +4446,9 @@ test.each(['abrupt', 'block', 'cancel'])('filesystem fixture gate preserves leak
4150
4446
  rmSync(output, { recursive: true, force: true });
4151
4447
  }
4152
4448
  });
4153
- test('filesystem fixture gate still enforces positive byte limits after deterministic contention', async () => {
4449
+ test.each(['node24', 'node26'])('filesystem fixture gate still enforces positive byte limits after deterministic contention on %s', async (runtime) => {
4154
4450
  const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-limit-')));
4155
- const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass') });
4451
+ const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass', runtime) });
4156
4452
  try {
4157
4453
  const row = await prepareIssueWorktree({
4158
4454
  baseRef: 'main',
@@ -4254,6 +4550,37 @@ test.each(['verification', 'runtime'])('filesystem fixture gate preserves observ
4254
4550
  rmSync(output, { recursive: true, force: true });
4255
4551
  }
4256
4552
  });
4553
+ test('Node 26 packed public exports preserve standalone and malformed-capability behavior', () => {
4554
+ const consumer = packedNode26Consumer();
4555
+ execFileSync(consumer.executable, [
4556
+ '-e',
4557
+ `
4558
+ (async () => {
4559
+ const assert = require('node:assert/strict');
4560
+ assert.equal(process.version, 'v26.2.0');
4561
+ const [root, agent, delivery, mcp] = await Promise.all([
4562
+ import('@aviaratech/ai-delivery'), import('@aviaratech/ai-delivery/agent'),
4563
+ import('@aviaratech/ai-delivery/delivery'), import('@aviaratech/ai-delivery/mcp'),
4564
+ ]);
4565
+ assert.equal(root.withVerificationFilesystemFixture, agent.withVerificationFilesystemFixture);
4566
+ assert.equal(typeof agent.verifyIssue, 'function');
4567
+ assert.equal(typeof delivery.loadValidRepositoryDeliveryEvidence, 'function');
4568
+ assert.equal(typeof mcp.createAiDeliveryMcpServer, 'function');
4569
+ const manifest = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8'));
4570
+ assert.deepEqual(Object.keys(manifest.exports).sort(), ['.', './agent', './delivery', './mcp']);
4571
+ const prior = process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
4572
+ delete process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
4573
+ assert.equal(await agent.withVerificationFilesystemFixture(() => 'standalone'), 'standalone');
4574
+ process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE = '{}';
4575
+ let executed = false;
4576
+ await assert.rejects(agent.withVerificationFilesystemFixture(() => { executed = true; }), /invalid or stale/);
4577
+ assert.equal(executed, false);
4578
+ if (prior === undefined) delete process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
4579
+ else process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE = prior;
4580
+ })().catch(error => { console.error(error); process.exitCode = 1; });
4581
+ `,
4582
+ ], { cwd: consumer.root, stdio: 'pipe' });
4583
+ });
4257
4584
  test('filesystem fixture helper runs standalone and fails closed on malformed capability', async () => {
4258
4585
  const { withVerificationFilesystemFixture } = await import('./agent.js');
4259
4586
  assert.equal(await withVerificationFilesystemFixture(() => 'standalone'), 'standalone');