@aviaratech/ai-delivery 0.3.10 → 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
|
package/dist/lifecycle.test.js
CHANGED
|
@@ -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 {
|
|
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';
|
|
@@ -4251,13 +4252,71 @@ test('fully cached bounded verification still rejects a newly leaked FIFO', asyn
|
|
|
4251
4252
|
rmSync(root, { recursive: true, force: true });
|
|
4252
4253
|
}
|
|
4253
4254
|
});
|
|
4254
|
-
|
|
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();
|
|
4255
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 = `
|
|
4256
4306
|
(async () => {
|
|
4257
4307
|
const fs = require('node:fs'), cp = require('node:child_process'), path = require('node:path'), assert = require('node:assert/strict');
|
|
4258
|
-
|
|
4259
|
-
const {
|
|
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)});
|
|
4260
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
|
+
};
|
|
4261
4320
|
const gate = JSON.parse(process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE);
|
|
4262
4321
|
const trace = value => fs.appendFileSync(events, value + '\\n');
|
|
4263
4322
|
await withVerificationFilesystemFixture(async () => {
|
|
@@ -4270,20 +4329,26 @@ function filesystemFixtureScript(output, mode) {
|
|
|
4270
4329
|
if (${JSON.stringify(mode)} === 'block') fs.readFileSync(fifo);
|
|
4271
4330
|
while (fs.readdirSync(path.join(gate.directory, 'requests')).length === 0) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4272
4331
|
assert.ok(fs.existsSync(fifo)); trace('scan-queued');
|
|
4273
|
-
|
|
4332
|
+
rejectPrivateFile(fifo); trace('reader-rejected');
|
|
4274
4333
|
const regular = path.join(output, 'regular'), hard = path.join(output, 'hard'), alias = path.join(output, 'alias');
|
|
4275
4334
|
fs.writeFileSync(regular, 'x'.repeat(128), { mode: 0o600 });
|
|
4276
|
-
fs.linkSync(regular, hard);
|
|
4277
|
-
fs.symlinkSync('./regular', alias);
|
|
4335
|
+
fs.linkSync(regular, hard); rejectPrivateFile(hard); fs.unlinkSync(hard);
|
|
4336
|
+
fs.symlinkSync('./regular', alias); rejectPrivateFile(alias); fs.unlinkSync(alias);
|
|
4278
4337
|
if (${JSON.stringify(mode)} === 'assertion') throw new Error('synthetic reader assertion failure');
|
|
4279
4338
|
} finally { fs.unlinkSync(fifo); trace('cleaned'); }
|
|
4280
4339
|
});
|
|
4281
4340
|
trace('released');
|
|
4282
4341
|
})().catch(error => { console.error(error.message); process.exitCode = 1; });`;
|
|
4342
|
+
return runtime === 'node26' ? node26ConsumerScript(script) : script;
|
|
4283
4343
|
}
|
|
4284
|
-
test.each([
|
|
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) => {
|
|
4285
4350
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-gate-')));
|
|
4286
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
|
|
4351
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
|
|
4287
4352
|
try {
|
|
4288
4353
|
const row = await prepareIssueWorktree({
|
|
4289
4354
|
baseRef: 'main',
|
|
@@ -4319,10 +4384,17 @@ test.each(['pass', 'assertion'])('filesystem fixture gate deterministically queu
|
|
|
4319
4384
|
rmSync(output, { recursive: true, force: true });
|
|
4320
4385
|
}
|
|
4321
4386
|
});
|
|
4322
|
-
test.each([
|
|
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) => {
|
|
4323
4395
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-failure-')));
|
|
4324
4396
|
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
4325
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
|
|
4397
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
|
|
4326
4398
|
const controller = new AbortController();
|
|
4327
4399
|
let running;
|
|
4328
4400
|
const progress = vi.spyOn(process.stderr, 'write');
|
|
@@ -4374,9 +4446,9 @@ test.each(['abrupt', 'block', 'cancel'])('filesystem fixture gate preserves leak
|
|
|
4374
4446
|
rmSync(output, { recursive: true, force: true });
|
|
4375
4447
|
}
|
|
4376
4448
|
});
|
|
4377
|
-
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) => {
|
|
4378
4450
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-limit-')));
|
|
4379
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass') });
|
|
4451
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass', runtime) });
|
|
4380
4452
|
try {
|
|
4381
4453
|
const row = await prepareIssueWorktree({
|
|
4382
4454
|
baseRef: 'main',
|
|
@@ -4478,6 +4550,37 @@ test.each(['verification', 'runtime'])('filesystem fixture gate preserves observ
|
|
|
4478
4550
|
rmSync(output, { recursive: true, force: true });
|
|
4479
4551
|
}
|
|
4480
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
|
+
});
|
|
4481
4584
|
test('filesystem fixture helper runs standalone and fails closed on malformed capability', async () => {
|
|
4482
4585
|
const { withVerificationFilesystemFixture } = await import('./agent.js');
|
|
4483
4586
|
assert.equal(await withVerificationFilesystemFixture(() => 'standalone'), 'standalone');
|