@aviaratech/ai-delivery 0.3.10 → 0.3.12
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 +10 -1
- package/dist/lifecycle.test.js +461 -22
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/verification.js +120 -12
- package/dist/verification.js.map +1 -1
- package/package.json +2 -2
- package/plugins/ai-delivery/.claude-plugin/plugin.json +1 -1
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
|
|
@@ -327,7 +336,7 @@ The `--admit` flag explicitly admits additional resource classes for execution.
|
|
|
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
|
|
|
330
|
-
During a running command,
|
|
339
|
+
During a running command, a previously validated, unchanged alias may temporarily lose its physical target during a rebuild. The observer checks its current identity and target ancestry, reports the path and errno, and still completes a fresh scan of all physical files in every declared root. It never substitutes an incomplete scan or an earlier byte count. A missing target does not impose a build deadline when complete output accounting remains available. Unvalidated aliases that prevent a complete scan retain the one-second observation-stall window, starting after the first failed scan rather than before it. Cancellation, RSS and disk checks continue. Baseline and final scans remain strict; permanent broken links, cycles, escaping aliases and changed identities fail closed. Failed commands retain captured stdout/stderr in the existing private content-addressed command-output store, with its digest, actual exit status and first/last filesystem observation in the failure diagnostic. Output still buffered inside a killed child is unavailable to the runner.
|
|
331
340
|
|
|
332
341
|
Real filesystem-negative tests can use `withVerificationFilesystemFixture` from `@aviaratech/ai-delivery` (or its `agent` export):
|
|
333
342
|
|
package/dist/lifecycle.test.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { execFileSync, spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
4
5
|
import { cpSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, utimesSync, writeFileSync, } from 'node:fs';
|
|
5
6
|
import { tmpdir } from 'node:os';
|
|
6
7
|
import { dirname, join } from 'node:path';
|
|
7
|
-
import {
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { afterAll, test, vi } from 'vitest';
|
|
8
10
|
import { syntheticDiscoveryClients, syntheticOverrides } from './fixtures/discovery.js';
|
|
9
11
|
import { Octokit } from '@octokit/rest';
|
|
10
12
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
@@ -4021,7 +4023,196 @@ test.each([
|
|
|
4021
4023
|
test('an unrelated consumer uses public MCP setup through verified configured-author publication, counted review readback, merge and cleanup', { timeout: 0 }, async () => {
|
|
4022
4024
|
await syntheticLifecycle({ remote: 'origin', divergentOrigin: false, producerOnboarding: true });
|
|
4023
4025
|
});
|
|
4024
|
-
|
|
4026
|
+
function ordinaryCliRebuildScript(output, mode = 'pass', target = join(output, 'cli.js')) {
|
|
4027
|
+
return `(async () => {
|
|
4028
|
+
const fs = require('node:fs');
|
|
4029
|
+
const trace = event => fs.appendFileSync(${JSON.stringify(join(output, 'events'))}, event + '\\n');
|
|
4030
|
+
trace('command-started'); fs.unlinkSync(${JSON.stringify(target)}); trace('target-removed');
|
|
4031
|
+
if (${JSON.stringify(dirname(target))} !== ${JSON.stringify(output)}) fs.rmdirSync(${JSON.stringify(dirname(target))});
|
|
4032
|
+
if (${JSON.stringify(mode)} === 'limit') fs.writeFileSync(${JSON.stringify(join(output, 'oversized'))}, 'x'.repeat(4096));
|
|
4033
|
+
for (let unit = 0; unit < 32; unit++) {
|
|
4034
|
+
fs.appendFileSync(${JSON.stringify(join(output, 'compiled'))}, 'build-unit\\n');
|
|
4035
|
+
trace('build-unit-' + unit);
|
|
4036
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4037
|
+
}
|
|
4038
|
+
if (${JSON.stringify(mode)} !== 'absent') {
|
|
4039
|
+
fs.mkdirSync(${JSON.stringify(dirname(target))}, { recursive: true });
|
|
4040
|
+
fs.writeFileSync(${JSON.stringify(target)}, 'rebuilt CLI'); trace('target-restored');
|
|
4041
|
+
}
|
|
4042
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4043
|
+
}
|
|
4044
|
+
test.each(['direct', 'chain', 'directory'])('ordinary bounded rebuild restores its %s CLI target after observable build work', async (shape) => {
|
|
4045
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-cli-rebuild-')));
|
|
4046
|
+
const { root } = await fixture();
|
|
4047
|
+
const target = shape === 'directory' ? join(output, 'dist', 'cli.js') : join(output, 'cli.js');
|
|
4048
|
+
const alias = join(output, 'cli');
|
|
4049
|
+
const events = join(output, 'events');
|
|
4050
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
4051
|
+
writeFileSync(target, 'original CLI');
|
|
4052
|
+
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
4053
|
+
if (shape === 'direct')
|
|
4054
|
+
symlinkSync('./cli.js', alias);
|
|
4055
|
+
else {
|
|
4056
|
+
symlinkSync(shape === 'chain' ? './cli.js' : './dist', join(output, 'intermediate'));
|
|
4057
|
+
symlinkSync(shape === 'chain' ? './intermediate' : './intermediate/cli.js', alias);
|
|
4058
|
+
}
|
|
4059
|
+
const observations = [];
|
|
4060
|
+
const duringAbsence = [];
|
|
4061
|
+
const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
|
|
4062
|
+
observations.push(String(line));
|
|
4063
|
+
if (!existsSync(target) && String(line).includes('sampledNewOutputBytes'))
|
|
4064
|
+
duringAbsence.push(JSON.parse(String(line).slice(String(line).indexOf('{'))));
|
|
4065
|
+
return true;
|
|
4066
|
+
});
|
|
4067
|
+
const script = ordinaryCliRebuildScript(output, 'pass', target);
|
|
4068
|
+
try {
|
|
4069
|
+
const { withRuntimeSetupWriter } = await import('./verification.js');
|
|
4070
|
+
await withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
|
|
4071
|
+
maxAggregateRssBytes: 1_000_000_000,
|
|
4072
|
+
minFreeDiskBytes: 1,
|
|
4073
|
+
maxNewOutputBytes: 8192,
|
|
4074
|
+
outputRoots: [output],
|
|
4075
|
+
}));
|
|
4076
|
+
assert.equal(readFileSync(target, 'utf8'), 'rebuilt CLI');
|
|
4077
|
+
assert.equal(readFileSync(join(output, 'compiled'), 'utf8').split('\n').length, 33);
|
|
4078
|
+
assert.equal(readFileSync(join(output, 'unrelated'), 'utf8'), 'preserved');
|
|
4079
|
+
assert.ok(duringAbsence.some((sample) => (sample.sampledNewOutputBytes ?? 0) > 0));
|
|
4080
|
+
assert.ok(duringAbsence.every((sample) => (sample.sampledAggregateRssBytes ?? 0) > 0));
|
|
4081
|
+
}
|
|
4082
|
+
finally {
|
|
4083
|
+
console.log(JSON.stringify({
|
|
4084
|
+
scenario: 'ordinary-cli-rebuild',
|
|
4085
|
+
shape,
|
|
4086
|
+
firstObservation: observations.find((line) => line.includes('ENOENT')),
|
|
4087
|
+
events: readFileSync(events, 'utf8').trim().split('\n'),
|
|
4088
|
+
targetRestored: existsSync(target),
|
|
4089
|
+
unrelatedPreserved: readFileSync(join(output, 'unrelated'), 'utf8') === 'preserved',
|
|
4090
|
+
}));
|
|
4091
|
+
progress.mockRestore();
|
|
4092
|
+
rmSync(root, { recursive: true, force: true });
|
|
4093
|
+
rmSync(output, { recursive: true, force: true });
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
4096
|
+
test.each([
|
|
4097
|
+
'bytes',
|
|
4098
|
+
'rss',
|
|
4099
|
+
'disk',
|
|
4100
|
+
'fifo',
|
|
4101
|
+
'permission',
|
|
4102
|
+
'identity',
|
|
4103
|
+
'ancestor',
|
|
4104
|
+
'cycle',
|
|
4105
|
+
'chain-identity',
|
|
4106
|
+
'directory-identity',
|
|
4107
|
+
'chain-cycle',
|
|
4108
|
+
])('a missing validated target retains complete scan and %s rejection across roots', async (mode) => {
|
|
4109
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-rebuild-boundary-')));
|
|
4110
|
+
const { root } = await fixture();
|
|
4111
|
+
const first = join(output, 'first'), second = join(output, 'second');
|
|
4112
|
+
mkdirSync(first);
|
|
4113
|
+
mkdirSync(second);
|
|
4114
|
+
const directory = join(first, 'dist'), target = join(directory, 'cli.js'), alias = join(first, 'cli');
|
|
4115
|
+
mkdirSync(directory);
|
|
4116
|
+
writeFileSync(target, 'CLI');
|
|
4117
|
+
const intermediate = join(first, 'intermediate');
|
|
4118
|
+
if (mode === 'chain-identity' || mode === 'chain-cycle') {
|
|
4119
|
+
symlinkSync('./dist/cli.js', intermediate);
|
|
4120
|
+
symlinkSync('./intermediate', alias);
|
|
4121
|
+
}
|
|
4122
|
+
else if (mode === 'directory-identity') {
|
|
4123
|
+
symlinkSync('./dist', intermediate);
|
|
4124
|
+
symlinkSync('./intermediate/cli.js', alias);
|
|
4125
|
+
}
|
|
4126
|
+
else
|
|
4127
|
+
symlinkSync('./dist/cli.js', alias);
|
|
4128
|
+
writeFileSync(join(second, 'unrelated'), 'preserved');
|
|
4129
|
+
const released = join(second, 'released');
|
|
4130
|
+
const observations = [];
|
|
4131
|
+
const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
|
|
4132
|
+
observations.push(String(line));
|
|
4133
|
+
if (String(line).includes('ENOENT'))
|
|
4134
|
+
writeFileSync(released, 'observed');
|
|
4135
|
+
return true;
|
|
4136
|
+
});
|
|
4137
|
+
const script = `(async () => {
|
|
4138
|
+
const fs = require('node:fs'), cp = require('node:child_process');
|
|
4139
|
+
fs.unlinkSync(${JSON.stringify(target)});
|
|
4140
|
+
while (!fs.existsSync(${JSON.stringify(released)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4141
|
+
if (${JSON.stringify(mode)} === 'bytes') fs.writeFileSync(${JSON.stringify(join(second, 'oversized'))}, 'x'.repeat(4096));
|
|
4142
|
+
if (${JSON.stringify(mode)} === 'rss') globalThis.retainedBuildMemory = Buffer.alloc(200 * 1024 * 1024, 1);
|
|
4143
|
+
if (${JSON.stringify(mode)} === 'disk') fs.writeFileSync(${JSON.stringify(join(second, 'disk-output'))}, Buffer.alloc(64 * 1024 * 1024, 1));
|
|
4144
|
+
if (${JSON.stringify(mode)} === 'fifo') cp.execFileSync('mkfifo', [${JSON.stringify(join(second, 'unsupported'))}]);
|
|
4145
|
+
if (${JSON.stringify(mode)} === 'permission') {
|
|
4146
|
+
fs.mkdirSync(${JSON.stringify(join(second, 'denied'))});
|
|
4147
|
+
fs.chmodSync(${JSON.stringify(join(second, 'denied'))}, 0);
|
|
4148
|
+
}
|
|
4149
|
+
if (${JSON.stringify(mode)} === 'identity') {
|
|
4150
|
+
fs.unlinkSync(${JSON.stringify(alias)}); fs.writeFileSync(${JSON.stringify(join(first, 'other'))}, 'other');
|
|
4151
|
+
fs.symlinkSync('./other', ${JSON.stringify(alias)});
|
|
4152
|
+
}
|
|
4153
|
+
if (${JSON.stringify(mode)} === 'ancestor') {
|
|
4154
|
+
fs.rmdirSync(${JSON.stringify(directory)}); fs.symlinkSync(${JSON.stringify(second)}, ${JSON.stringify(directory)});
|
|
4155
|
+
}
|
|
4156
|
+
if (${JSON.stringify(mode)} === 'cycle') fs.symlinkSync('./cli.js', ${JSON.stringify(target)});
|
|
4157
|
+
if (${JSON.stringify(mode)} === 'chain-identity') {
|
|
4158
|
+
fs.unlinkSync(${JSON.stringify(intermediate)}); fs.writeFileSync(${JSON.stringify(join(first, 'other'))}, 'other');
|
|
4159
|
+
fs.symlinkSync('./other', ${JSON.stringify(intermediate)});
|
|
4160
|
+
}
|
|
4161
|
+
if (${JSON.stringify(mode)} === 'directory-identity') {
|
|
4162
|
+
fs.unlinkSync(${JSON.stringify(intermediate)}); fs.mkdirSync(${JSON.stringify(join(first, 'other'))});
|
|
4163
|
+
fs.writeFileSync(${JSON.stringify(join(first, 'other', 'cli.js'))}, 'other');
|
|
4164
|
+
fs.symlinkSync('./other', ${JSON.stringify(intermediate)});
|
|
4165
|
+
}
|
|
4166
|
+
if (${JSON.stringify(mode)} === 'chain-cycle') {
|
|
4167
|
+
fs.unlinkSync(${JSON.stringify(intermediate)}); fs.symlinkSync('./cli', ${JSON.stringify(intermediate)});
|
|
4168
|
+
}
|
|
4169
|
+
for (let unit = 0; unit < 32; unit++) {
|
|
4170
|
+
fs.appendFileSync(${JSON.stringify(join(second, 'work'))}, 'unit\\n');
|
|
4171
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4172
|
+
}
|
|
4173
|
+
fs.writeFileSync(${JSON.stringify(target)}, 'rebuilt CLI');
|
|
4174
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4175
|
+
try {
|
|
4176
|
+
const { withRuntimeSetupWriter } = await import('./verification.js');
|
|
4177
|
+
const disk = (await import('node:fs')).statfsSync(output);
|
|
4178
|
+
await assert.rejects(withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
|
|
4179
|
+
maxAggregateRssBytes: mode === 'rss' ? 128 * 1024 * 1024 : 1_000_000_000,
|
|
4180
|
+
minFreeDiskBytes: mode === 'disk' ? disk.bavail * disk.bsize - 32 * 1024 * 1024 : 1,
|
|
4181
|
+
maxNewOutputBytes: mode === 'disk' ? 128 * 1024 * 1024 : 1024,
|
|
4182
|
+
outputRoots: [first, second],
|
|
4183
|
+
})), (error) => {
|
|
4184
|
+
const message = String(error);
|
|
4185
|
+
assert.match(message, mode === 'bytes'
|
|
4186
|
+
? /filesystem output.*exceeded/u
|
|
4187
|
+
: mode === 'rss'
|
|
4188
|
+
? /aggregate RSS.*exceeded/u
|
|
4189
|
+
: mode === 'disk'
|
|
4190
|
+
? /Free disk fell below/u
|
|
4191
|
+
: mode === 'fifo'
|
|
4192
|
+
? /unsupported file/u
|
|
4193
|
+
: mode === 'permission'
|
|
4194
|
+
? /readdir failed.*EACCES/u
|
|
4195
|
+
: mode === 'cycle' || mode === 'chain-cycle'
|
|
4196
|
+
? /ELOOP/u
|
|
4197
|
+
: /identity changed/u);
|
|
4198
|
+
assert.match(message, /First filesystem observation:.*ENOENT.*Last filesystem observation:/u);
|
|
4199
|
+
assert.doesNotMatch(message, /cleanup failed/u);
|
|
4200
|
+
return true;
|
|
4201
|
+
});
|
|
4202
|
+
assert.ok(observations.some((line) => line.includes('ENOENT')));
|
|
4203
|
+
assert.equal(observations.some((line) => line.includes('"state":"completed"')), false);
|
|
4204
|
+
assert.equal(readFileSync(join(second, 'unrelated'), 'utf8'), 'preserved');
|
|
4205
|
+
}
|
|
4206
|
+
finally {
|
|
4207
|
+
const denied = join(second, 'denied');
|
|
4208
|
+
if (existsSync(denied))
|
|
4209
|
+
chmodSync(denied, 0o700);
|
|
4210
|
+
progress.mockRestore();
|
|
4211
|
+
rmSync(root, { recursive: true, force: true });
|
|
4212
|
+
rmSync(output, { recursive: true, force: true });
|
|
4213
|
+
}
|
|
4214
|
+
});
|
|
4215
|
+
test.each(['pass', 'chain-pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary owned symlink teardown preserves observation and %s command evidence', async (mode) => {
|
|
4025
4216
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-ordinary-teardown-')));
|
|
4026
4217
|
const { root } = await fixture();
|
|
4027
4218
|
const controller = new AbortController();
|
|
@@ -4030,7 +4221,9 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4030
4221
|
const release = join(output, 'release');
|
|
4031
4222
|
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
4032
4223
|
writeFileSync(join(output, 'target'), 'x'.repeat(128));
|
|
4033
|
-
|
|
4224
|
+
if (mode === 'chain-pass')
|
|
4225
|
+
symlinkSync('./target', join(output, 'intermediate'));
|
|
4226
|
+
symlinkSync(mode === 'chain-pass' ? './intermediate' : './target', join(output, 'alias'));
|
|
4034
4227
|
let observedGap = false;
|
|
4035
4228
|
const observations = [];
|
|
4036
4229
|
let releaseTimer;
|
|
@@ -4043,7 +4236,7 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4043
4236
|
releaseTimer = setTimeout(() => {
|
|
4044
4237
|
if (mode === 'cancel')
|
|
4045
4238
|
controller.abort();
|
|
4046
|
-
else
|
|
4239
|
+
else
|
|
4047
4240
|
writeFileSync(release, 'continue');
|
|
4048
4241
|
}, 100);
|
|
4049
4242
|
}
|
|
@@ -4059,7 +4252,10 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4059
4252
|
trace('target-removed'); fs.writeFileSync(${JSON.stringify(ready)}, 'ready');
|
|
4060
4253
|
if (${JSON.stringify(mode)} === 'limit') fs.writeFileSync(${JSON.stringify(join(output, 'retained'))}, 'x'.repeat(512));
|
|
4061
4254
|
while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4062
|
-
|
|
4255
|
+
if (${JSON.stringify(mode)} !== 'persistent') {
|
|
4256
|
+
fs.unlinkSync(${JSON.stringify(join(output, 'alias'))}); trace('alias-removed');
|
|
4257
|
+
if (${JSON.stringify(mode)} === 'chain-pass') fs.unlinkSync(${JSON.stringify(join(output, 'intermediate'))});
|
|
4258
|
+
}
|
|
4063
4259
|
process.exitCode = ${mode === 'command-error' ? 7 : 0};
|
|
4064
4260
|
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4065
4261
|
try {
|
|
@@ -4070,7 +4266,7 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4070
4266
|
maxNewOutputBytes: mode === 'limit' ? 256 : 4096,
|
|
4071
4267
|
outputRoots: [output],
|
|
4072
4268
|
}, controller.signal));
|
|
4073
|
-
if (mode === 'pass')
|
|
4269
|
+
if (mode === 'pass' || mode === 'chain-pass')
|
|
4074
4270
|
await run;
|
|
4075
4271
|
else
|
|
4076
4272
|
await assert.rejects(run, (error) => {
|
|
@@ -4078,7 +4274,7 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4078
4274
|
assert.match(message, mode === 'cancel'
|
|
4079
4275
|
? /cancelled/u
|
|
4080
4276
|
: mode === 'persistent'
|
|
4081
|
-
? /
|
|
4277
|
+
? /broken or cyclic symbolic link.*ENOENT/u
|
|
4082
4278
|
: mode === 'limit'
|
|
4083
4279
|
? /filesystem output.*exceeded/u
|
|
4084
4280
|
: /exit 7/u);
|
|
@@ -4107,7 +4303,7 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4107
4303
|
unrelatedPreserved: true,
|
|
4108
4304
|
aliasRetained: readdirSync(output).includes('alias'),
|
|
4109
4305
|
}));
|
|
4110
|
-
assert.deepEqual(readFileSync(events, 'utf8').trim().split('\n'), mode === 'cancel' || mode === 'persistent'
|
|
4306
|
+
assert.deepEqual(readFileSync(events, 'utf8').trim().split('\n'), mode === 'cancel' || mode === 'persistent' || mode === 'limit'
|
|
4111
4307
|
? ['command-started', 'target-removed']
|
|
4112
4308
|
: ['command-started', 'target-removed', 'alias-removed']);
|
|
4113
4309
|
if (mode === 'cancel' || mode === 'persistent') {
|
|
@@ -4132,12 +4328,68 @@ test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary
|
|
|
4132
4328
|
rmSync(output, { recursive: true, force: true });
|
|
4133
4329
|
}
|
|
4134
4330
|
});
|
|
4135
|
-
test('injected
|
|
4331
|
+
test('injected first failed scan duration does not consume the subsequent observation stall window', async () => {
|
|
4332
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-scan-duration-')));
|
|
4333
|
+
const { root } = await fixture();
|
|
4334
|
+
const target = join(output, 'target'), alias = join(output, 'alias'), release = join(output, 'release');
|
|
4335
|
+
writeFileSync(target, 'target');
|
|
4336
|
+
const fs = (await import('node:fs')).default;
|
|
4337
|
+
const actualRealpath = fs.realpathSync;
|
|
4338
|
+
const now = Date.now.bind(Date);
|
|
4339
|
+
let offset = 0, delayed = false, firstObservation;
|
|
4340
|
+
const clock = vi.spyOn(Date, 'now').mockImplementation(() => now() + offset);
|
|
4341
|
+
const scan = vi.spyOn(fs, 'realpathSync').mockImplementation((path, options) => {
|
|
4342
|
+
try {
|
|
4343
|
+
return actualRealpath(path, options);
|
|
4344
|
+
}
|
|
4345
|
+
catch (error) {
|
|
4346
|
+
if (path === alias && !delayed) {
|
|
4347
|
+
delayed = true;
|
|
4348
|
+
offset = 1500;
|
|
4349
|
+
}
|
|
4350
|
+
throw error;
|
|
4351
|
+
}
|
|
4352
|
+
});
|
|
4353
|
+
syncBuiltinESMExports();
|
|
4354
|
+
const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
|
|
4355
|
+
if (firstObservation === undefined && String(line).includes('ENOENT')) {
|
|
4356
|
+
firstObservation = String(line);
|
|
4357
|
+
unlinkSync(alias);
|
|
4358
|
+
writeFileSync(release, 'finish');
|
|
4359
|
+
}
|
|
4360
|
+
return true;
|
|
4361
|
+
});
|
|
4362
|
+
const script = `(async () => {
|
|
4363
|
+
const fs = require('node:fs'); fs.unlinkSync(${JSON.stringify(target)});
|
|
4364
|
+
fs.symlinkSync('./target', ${JSON.stringify(alias)});
|
|
4365
|
+
while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4366
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4367
|
+
try {
|
|
4368
|
+
const { withRuntimeSetupWriter } = await import('./verification.js');
|
|
4369
|
+
await withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
|
|
4370
|
+
maxAggregateRssBytes: 1_000_000_000,
|
|
4371
|
+
minFreeDiskBytes: 1,
|
|
4372
|
+
maxNewOutputBytes: 4096,
|
|
4373
|
+
outputRoots: [output],
|
|
4374
|
+
}));
|
|
4375
|
+
assert.ok(delayed);
|
|
4376
|
+
assert.ok(firstObservation);
|
|
4377
|
+
assert.equal(existsSync(alias), false);
|
|
4378
|
+
}
|
|
4379
|
+
finally {
|
|
4380
|
+
progress.mockRestore();
|
|
4381
|
+
scan.mockRestore();
|
|
4382
|
+
syncBuiltinESMExports();
|
|
4383
|
+
clock.mockRestore();
|
|
4384
|
+
rmSync(root, { recursive: true, force: true });
|
|
4385
|
+
rmSync(output, { recursive: true, force: true });
|
|
4386
|
+
}
|
|
4387
|
+
});
|
|
4388
|
+
test('injected observation stall rejects a late complete retry for an unvalidated alias', async () => {
|
|
4136
4389
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-observation-deadline-')));
|
|
4137
4390
|
const { root } = await fixture();
|
|
4138
4391
|
const target = join(output, 'target'), alias = join(output, 'alias'), release = join(output, 'release');
|
|
4139
4392
|
writeFileSync(target, 'target');
|
|
4140
|
-
symlinkSync('./target', alias);
|
|
4141
4393
|
const now = Date.now.bind(Date);
|
|
4142
4394
|
let offset = 0;
|
|
4143
4395
|
let firstObservation;
|
|
@@ -4157,6 +4409,7 @@ test('injected observation deadline rejects a late complete real filesystem retr
|
|
|
4157
4409
|
});
|
|
4158
4410
|
const script = `(async () => {
|
|
4159
4411
|
const fs = require('node:fs'); fs.unlinkSync(${JSON.stringify(target)});
|
|
4412
|
+
fs.symlinkSync('./target', ${JSON.stringify(alias)});
|
|
4160
4413
|
while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4161
4414
|
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4162
4415
|
try {
|
|
@@ -4251,13 +4504,155 @@ test('fully cached bounded verification still rejects a newly leaked FIFO', asyn
|
|
|
4251
4504
|
rmSync(root, { recursive: true, force: true });
|
|
4252
4505
|
}
|
|
4253
4506
|
});
|
|
4254
|
-
|
|
4507
|
+
let node26Package;
|
|
4508
|
+
function packedNode26Consumer() {
|
|
4509
|
+
if (node26Package !== undefined)
|
|
4510
|
+
return node26Package;
|
|
4511
|
+
assert.equal(process.version, 'v24.21.0', 'canonical verification controller must remain Node 24.21.0');
|
|
4512
|
+
const executable = process.env['AI_DELIVERY_NODE26_EXECUTABLE'];
|
|
4513
|
+
assert.ok(executable, 'set AI_DELIVERY_NODE26_EXECUTABLE to the actual Node 26.2.0 executable');
|
|
4514
|
+
assert.equal(execFileSync(executable, ['--version'], { encoding: 'utf8' }).trim(), 'v26.2.0');
|
|
4515
|
+
const temporary = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-node26-')));
|
|
4516
|
+
try {
|
|
4517
|
+
const source = fileURLToPath(new URL('../', import.meta.url));
|
|
4518
|
+
const pack = JSON.parse(execFileSync('npm', ['pack', '--json', '--pack-destination', temporary], {
|
|
4519
|
+
cwd: source,
|
|
4520
|
+
encoding: 'utf8',
|
|
4521
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
4522
|
+
}));
|
|
4523
|
+
execFileSync('tar', ['-xzf', join(temporary, pack[0].filename), '-C', temporary]);
|
|
4524
|
+
const root = join(temporary, 'package');
|
|
4525
|
+
symlinkSync(join(source, 'node_modules'), join(root, 'node_modules'), 'dir');
|
|
4526
|
+
process.stderr.write(`ai-delivery.node26-consumer ${JSON.stringify({
|
|
4527
|
+
controller: process.version,
|
|
4528
|
+
consumer: 'v26.2.0',
|
|
4529
|
+
archiveSha256: createHash('sha256')
|
|
4530
|
+
.update(readFileSync(join(temporary, pack[0].filename)))
|
|
4531
|
+
.digest('hex'),
|
|
4532
|
+
manifestSha256: createHash('sha256')
|
|
4533
|
+
.update(readFileSync(join(root, 'package.json')))
|
|
4534
|
+
.digest('hex'),
|
|
4535
|
+
})}\n`);
|
|
4536
|
+
node26Package = { executable: realpathSync(executable), root, temporary };
|
|
4537
|
+
return node26Package;
|
|
4538
|
+
}
|
|
4539
|
+
catch (error) {
|
|
4540
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
4541
|
+
throw error;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
afterAll(() => {
|
|
4545
|
+
if (node26Package !== undefined)
|
|
4546
|
+
rmSync(node26Package.temporary, { recursive: true, force: true });
|
|
4547
|
+
});
|
|
4548
|
+
function node26ConsumerScript(script) {
|
|
4549
|
+
const consumer = packedNode26Consumer();
|
|
4255
4550
|
return `
|
|
4551
|
+
require('node:assert/strict').equal(process.version, 'v24.21.0');
|
|
4552
|
+
const child = require('node:child_process').spawnSync(${JSON.stringify(consumer.executable)}, ['-e', ${JSON.stringify(script)}], { cwd: ${JSON.stringify(consumer.root)}, stdio: 'inherit' });
|
|
4553
|
+
if (child.error) throw child.error;
|
|
4554
|
+
process.exit(child.status ?? 1);`;
|
|
4555
|
+
}
|
|
4556
|
+
test.each(['pass', 'absent', 'limit', 'cancel'])('Node 26 packed public verification preserves CLI rebuild %s behavior', async (mode) => {
|
|
4557
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-packed-rebuild-')));
|
|
4558
|
+
const target = join(output, 'cli.js');
|
|
4559
|
+
writeFileSync(target, 'original CLI');
|
|
4560
|
+
symlinkSync('./cli.js', join(output, 'cli'));
|
|
4561
|
+
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
4562
|
+
const { root } = await fixture({ personalAuthor: true, firstStageScript: ordinaryCliRebuildScript(output, mode) });
|
|
4563
|
+
try {
|
|
4564
|
+
const row = await prepareIssueWorktree({
|
|
4565
|
+
baseRef: 'main',
|
|
4566
|
+
identity: 'host-author',
|
|
4567
|
+
issueNumber: 17,
|
|
4568
|
+
repoRoot: root,
|
|
4569
|
+
});
|
|
4570
|
+
const clients = syntheticDiscoveryClients();
|
|
4571
|
+
const replies = [];
|
|
4572
|
+
for (const fragment of [
|
|
4573
|
+
'query DeliveryRepository',
|
|
4574
|
+
'query ProjectDeliveryConfiguration',
|
|
4575
|
+
'issueFields(first:',
|
|
4576
|
+
'issueTypes(first:',
|
|
4577
|
+
])
|
|
4578
|
+
replies.push({ fragment, data: await clients.graphql(fragment) });
|
|
4579
|
+
const consumer = packedNode26Consumer();
|
|
4580
|
+
const result = execFileSync(consumer.executable, [
|
|
4581
|
+
'-e',
|
|
4582
|
+
`(async () => {
|
|
4583
|
+
const assert = require('node:assert/strict'), fs = require('node:fs');
|
|
4584
|
+
assert.equal(process.version, 'v26.2.0');
|
|
4585
|
+
const replies = ${JSON.stringify(replies)};
|
|
4586
|
+
// Only GitHub discovery transport is synthetic; package, writer, scanner and child filesystem are real.
|
|
4587
|
+
globalThis.fetch = async (url, options) => {
|
|
4588
|
+
assert.equal(String(url), 'https://api.github.com/graphql');
|
|
4589
|
+
const query = JSON.parse(options.body).query;
|
|
4590
|
+
const reply = replies.find(reply => query.includes(reply.fragment));
|
|
4591
|
+
assert.ok(reply, 'unexpected synthetic discovery query');
|
|
4592
|
+
return new Response(JSON.stringify({ data: reply.data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
4593
|
+
};
|
|
4594
|
+
process.env.AUTHOR_TOKEN = 'synthetic-author-token';
|
|
4595
|
+
const { verifyIssue } = await import('@aviaratech/ai-delivery/agent');
|
|
4596
|
+
const controller = new AbortController();
|
|
4597
|
+
const observations = []; const write = process.stderr.write.bind(process.stderr);
|
|
4598
|
+
process.stderr.write = line => {
|
|
4599
|
+
observations.push(String(line));
|
|
4600
|
+
if (${JSON.stringify(mode)} === 'cancel' && String(line).includes('ENOENT')) controller.abort();
|
|
4601
|
+
return true;
|
|
4602
|
+
};
|
|
4603
|
+
try {
|
|
4604
|
+
const running = verifyIssue({ issueNumber: 17, repoRoot: ${JSON.stringify(row.path)}, signal: controller.signal,
|
|
4605
|
+
resourceBounds: { maxAggregateRssBytes: 1000000000, minFreeDiskBytes: 1,
|
|
4606
|
+
maxNewOutputBytes: ${mode === 'limit' ? 1024 : 8192}, outputRoots: [${JSON.stringify(output)}] } });
|
|
4607
|
+
if (${JSON.stringify(mode)} === 'pass') {
|
|
4608
|
+
const run = await running;
|
|
4609
|
+
assert.ok(run.resources.sampleCount > 0); assert.ok(run.resources.maxSampledNewOutputBytes > 0);
|
|
4610
|
+
assert.equal(fs.readFileSync(${JSON.stringify(target)}, 'utf8'), 'rebuilt CLI');
|
|
4611
|
+
// Compatible completed proof is reused, but the current final filesystem is still strictly scanned.
|
|
4612
|
+
fs.unlinkSync(${JSON.stringify(target)});
|
|
4613
|
+
await assert.rejects(verifyIssue({ issueNumber: 17, repoRoot: ${JSON.stringify(row.path)},
|
|
4614
|
+
resourceBounds: { maxAggregateRssBytes: 1000000000, minFreeDiskBytes: 1, maxNewOutputBytes: 8192,
|
|
4615
|
+
outputRoots: [${JSON.stringify(output)}] } }), /broken or cyclic symbolic link/);
|
|
4616
|
+
} else {
|
|
4617
|
+
await assert.rejects(running, error => {
|
|
4618
|
+
assert.match(String(error), ${mode === 'cancel' ? '/cancelled/' : mode === 'limit' ? '/filesystem output.*exceeded/' : '/broken or cyclic symbolic link/'});
|
|
4619
|
+
assert.doesNotMatch(String(error), /cleanup failed/); return true;
|
|
4620
|
+
});
|
|
4621
|
+
}
|
|
4622
|
+
assert.ok(observations.some(line => line.includes('ENOENT')));
|
|
4623
|
+
assert.equal(fs.readFileSync(${JSON.stringify(join(output, 'unrelated'))}, 'utf8'), 'preserved');
|
|
4624
|
+
console.log(JSON.stringify({ scenario: 'packed-node26-cli-rebuild', mode: ${JSON.stringify(mode)},
|
|
4625
|
+
firstObservation: observations.find(line => line.includes('ENOENT')), events: fs.readFileSync(${JSON.stringify(join(output, 'events'))}, 'utf8').trim().split('\\n') }));
|
|
4626
|
+
} finally { controller.abort(); process.stderr.write = write; }
|
|
4627
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`,
|
|
4628
|
+
], {
|
|
4629
|
+
cwd: consumer.root,
|
|
4630
|
+
encoding: 'utf8',
|
|
4631
|
+
maxBuffer: 1024 * 1024,
|
|
4632
|
+
});
|
|
4633
|
+
console.log(result.trim());
|
|
4634
|
+
}
|
|
4635
|
+
finally {
|
|
4636
|
+
rmSync(root, { recursive: true, force: true });
|
|
4637
|
+
rmSync(output, { recursive: true, force: true });
|
|
4638
|
+
}
|
|
4639
|
+
});
|
|
4640
|
+
function filesystemFixtureScript(output, mode, runtime = 'node24') {
|
|
4641
|
+
const script = `
|
|
4256
4642
|
(async () => {
|
|
4257
4643
|
const fs = require('node:fs'), cp = require('node:child_process'), path = require('node:path'), assert = require('node:assert/strict');
|
|
4258
|
-
|
|
4259
|
-
const {
|
|
4644
|
+
assert.equal(process.version, ${JSON.stringify(runtime === 'node26' ? 'v26.2.0' : 'v24.21.0')});
|
|
4645
|
+
const { withVerificationFilesystemFixture } = await import(${JSON.stringify(runtime === 'node26' ? '@aviaratech/ai-delivery/agent' : new URL('./agent.js', import.meta.url).href)});
|
|
4646
|
+
const { loadValidRepositoryDeliveryEvidence } = await import(${JSON.stringify(runtime === 'node26' ? '@aviaratech/ai-delivery/delivery' : new URL('./delivery/index.js', import.meta.url).href)});
|
|
4260
4647
|
const output = ${JSON.stringify(output)}, fifo = path.join(output, 'fifo'), events = path.join(output, 'events');
|
|
4648
|
+
const evidenceId = 'sha256:' + 'a'.repeat(64), currentHead = { sha: 'b'.repeat(40), tree: 'c'.repeat(40) };
|
|
4649
|
+
const evidence = path.join(output, 'ai-delivery', 'receipts', 'delivery@1', currentHead.sha, evidenceId.slice(7) + '.json');
|
|
4650
|
+
fs.mkdirSync(path.dirname(evidence), { recursive: true });
|
|
4651
|
+
const rejectPrivateFile = file => {
|
|
4652
|
+
fs.renameSync(file, evidence);
|
|
4653
|
+
try { assert.throws(() => loadValidRepositoryDeliveryEvidence({ createInput: { gitCommonDir: output, currentHead }, evidenceId }), /private regular file/); }
|
|
4654
|
+
finally { fs.renameSync(evidence, file); }
|
|
4655
|
+
};
|
|
4261
4656
|
const gate = JSON.parse(process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE);
|
|
4262
4657
|
const trace = value => fs.appendFileSync(events, value + '\\n');
|
|
4263
4658
|
await withVerificationFilesystemFixture(async () => {
|
|
@@ -4270,20 +4665,26 @@ function filesystemFixtureScript(output, mode) {
|
|
|
4270
4665
|
if (${JSON.stringify(mode)} === 'block') fs.readFileSync(fifo);
|
|
4271
4666
|
while (fs.readdirSync(path.join(gate.directory, 'requests')).length === 0) await new Promise(resolve => setTimeout(resolve, 10));
|
|
4272
4667
|
assert.ok(fs.existsSync(fifo)); trace('scan-queued');
|
|
4273
|
-
|
|
4668
|
+
rejectPrivateFile(fifo); trace('reader-rejected');
|
|
4274
4669
|
const regular = path.join(output, 'regular'), hard = path.join(output, 'hard'), alias = path.join(output, 'alias');
|
|
4275
4670
|
fs.writeFileSync(regular, 'x'.repeat(128), { mode: 0o600 });
|
|
4276
|
-
fs.linkSync(regular, hard);
|
|
4277
|
-
fs.symlinkSync('./regular', alias);
|
|
4671
|
+
fs.linkSync(regular, hard); rejectPrivateFile(hard); fs.unlinkSync(hard);
|
|
4672
|
+
fs.symlinkSync('./regular', alias); rejectPrivateFile(alias); fs.unlinkSync(alias);
|
|
4278
4673
|
if (${JSON.stringify(mode)} === 'assertion') throw new Error('synthetic reader assertion failure');
|
|
4279
4674
|
} finally { fs.unlinkSync(fifo); trace('cleaned'); }
|
|
4280
4675
|
});
|
|
4281
4676
|
trace('released');
|
|
4282
4677
|
})().catch(error => { console.error(error.message); process.exitCode = 1; });`;
|
|
4678
|
+
return runtime === 'node26' ? node26ConsumerScript(script) : script;
|
|
4283
4679
|
}
|
|
4284
|
-
test.each([
|
|
4680
|
+
test.each([
|
|
4681
|
+
['pass', 'node24'],
|
|
4682
|
+
['assertion', 'node24'],
|
|
4683
|
+
['pass', 'node26'],
|
|
4684
|
+
['assertion', 'node26'],
|
|
4685
|
+
])('filesystem fixture gate deterministically queues a scan through reader rejection and %s teardown on %s', async (mode, runtime) => {
|
|
4285
4686
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-gate-')));
|
|
4286
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
|
|
4687
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
|
|
4287
4688
|
try {
|
|
4288
4689
|
const row = await prepareIssueWorktree({
|
|
4289
4690
|
baseRef: 'main',
|
|
@@ -4319,10 +4720,17 @@ test.each(['pass', 'assertion'])('filesystem fixture gate deterministically queu
|
|
|
4319
4720
|
rmSync(output, { recursive: true, force: true });
|
|
4320
4721
|
}
|
|
4321
4722
|
});
|
|
4322
|
-
test.each([
|
|
4723
|
+
test.each([
|
|
4724
|
+
['abrupt', 'node24'],
|
|
4725
|
+
['block', 'node24'],
|
|
4726
|
+
['cancel', 'node24'],
|
|
4727
|
+
['abrupt', 'node26'],
|
|
4728
|
+
['block', 'node26'],
|
|
4729
|
+
['cancel', 'node26'],
|
|
4730
|
+
])('filesystem fixture gate preserves leaked FIFO and unrelated files after %s on %s', async (mode, runtime) => {
|
|
4323
4731
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-failure-')));
|
|
4324
4732
|
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
4325
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode) });
|
|
4733
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, mode, runtime) });
|
|
4326
4734
|
const controller = new AbortController();
|
|
4327
4735
|
let running;
|
|
4328
4736
|
const progress = vi.spyOn(process.stderr, 'write');
|
|
@@ -4374,9 +4782,9 @@ test.each(['abrupt', 'block', 'cancel'])('filesystem fixture gate preserves leak
|
|
|
4374
4782
|
rmSync(output, { recursive: true, force: true });
|
|
4375
4783
|
}
|
|
4376
4784
|
});
|
|
4377
|
-
test('filesystem fixture gate still enforces positive byte limits after deterministic contention', async () => {
|
|
4785
|
+
test.each(['node24', 'node26'])('filesystem fixture gate still enforces positive byte limits after deterministic contention on %s', async (runtime) => {
|
|
4378
4786
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-fixture-limit-')));
|
|
4379
|
-
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass') });
|
|
4787
|
+
const { root } = await fixture({ firstStageScript: filesystemFixtureScript(output, 'pass', runtime) });
|
|
4380
4788
|
try {
|
|
4381
4789
|
const row = await prepareIssueWorktree({
|
|
4382
4790
|
baseRef: 'main',
|
|
@@ -4478,6 +4886,37 @@ test.each(['verification', 'runtime'])('filesystem fixture gate preserves observ
|
|
|
4478
4886
|
rmSync(output, { recursive: true, force: true });
|
|
4479
4887
|
}
|
|
4480
4888
|
});
|
|
4889
|
+
test('Node 26 packed public exports preserve standalone and malformed-capability behavior', () => {
|
|
4890
|
+
const consumer = packedNode26Consumer();
|
|
4891
|
+
execFileSync(consumer.executable, [
|
|
4892
|
+
'-e',
|
|
4893
|
+
`
|
|
4894
|
+
(async () => {
|
|
4895
|
+
const assert = require('node:assert/strict');
|
|
4896
|
+
assert.equal(process.version, 'v26.2.0');
|
|
4897
|
+
const [root, agent, delivery, mcp] = await Promise.all([
|
|
4898
|
+
import('@aviaratech/ai-delivery'), import('@aviaratech/ai-delivery/agent'),
|
|
4899
|
+
import('@aviaratech/ai-delivery/delivery'), import('@aviaratech/ai-delivery/mcp'),
|
|
4900
|
+
]);
|
|
4901
|
+
assert.equal(root.withVerificationFilesystemFixture, agent.withVerificationFilesystemFixture);
|
|
4902
|
+
assert.equal(typeof agent.verifyIssue, 'function');
|
|
4903
|
+
assert.equal(typeof delivery.loadValidRepositoryDeliveryEvidence, 'function');
|
|
4904
|
+
assert.equal(typeof mcp.createAiDeliveryMcpServer, 'function');
|
|
4905
|
+
const manifest = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8'));
|
|
4906
|
+
assert.deepEqual(Object.keys(manifest.exports).sort(), ['.', './agent', './delivery', './mcp']);
|
|
4907
|
+
const prior = process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
|
|
4908
|
+
delete process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
|
|
4909
|
+
assert.equal(await agent.withVerificationFilesystemFixture(() => 'standalone'), 'standalone');
|
|
4910
|
+
process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE = '{}';
|
|
4911
|
+
let executed = false;
|
|
4912
|
+
await assert.rejects(agent.withVerificationFilesystemFixture(() => { executed = true; }), /invalid or stale/);
|
|
4913
|
+
assert.equal(executed, false);
|
|
4914
|
+
if (prior === undefined) delete process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
|
|
4915
|
+
else process.env.AI_DELIVERY_OUTPUT_OBSERVATION_GATE = prior;
|
|
4916
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
4917
|
+
`,
|
|
4918
|
+
], { cwd: consumer.root, stdio: 'pipe' });
|
|
4919
|
+
});
|
|
4481
4920
|
test('filesystem fixture helper runs standalone and fails closed on malformed capability', async () => {
|
|
4482
4921
|
const { withVerificationFilesystemFixture } = await import('./agent.js');
|
|
4483
4922
|
assert.equal(await withVerificationFilesystemFixture(() => 'standalone'), 'standalone');
|