@aviaratech/ai-delivery 0.3.8 → 0.3.9
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
|
@@ -327,6 +327,8 @@ The `--admit` flag explicitly admits additional resource classes. Publication cr
|
|
|
327
327
|
|
|
328
328
|
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
329
|
|
|
330
|
+
During a running command, an `ENOENT` from symlink resolution can reflect ordinary tree removal. The observer reports the first path and errno and retries complete scans for at most one second; it never accepts a partial scan or resets that observation-stall window. Cancellation interrupts the wait, and RSS and disk checks continue. Baseline and final scans remain strict; persistent broken links, cycles and escaping aliases fail closed. Failed commands retain captured stdout/stderr in the existing private content-addressed command-output store, with its digest and the actual exit status in the failure diagnostic. Output still buffered inside a killed child is unavailable to the runner.
|
|
331
|
+
|
|
330
332
|
Real filesystem-negative tests can use `withVerificationFilesystemFixture` from `@aviaratech/ai-delivery` (or its `agent` export):
|
|
331
333
|
|
|
332
334
|
```js
|
package/dist/lifecycle.test.js
CHANGED
|
@@ -3797,6 +3797,170 @@ test.each([
|
|
|
3797
3797
|
test('an unrelated consumer uses public MCP setup through verified configured-author publication, counted review readback, merge and cleanup', { timeout: 0 }, async () => {
|
|
3798
3798
|
await syntheticLifecycle({ remote: 'origin', divergentOrigin: false, producerOnboarding: true });
|
|
3799
3799
|
});
|
|
3800
|
+
test.each(['pass', 'command-error', 'cancel', 'persistent', 'limit'])('ordinary owned symlink teardown preserves observation and %s command evidence', async (mode) => {
|
|
3801
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-ordinary-teardown-')));
|
|
3802
|
+
const { root } = await fixture();
|
|
3803
|
+
const controller = new AbortController();
|
|
3804
|
+
const events = join(output, 'events');
|
|
3805
|
+
const ready = join(output, 'ready');
|
|
3806
|
+
const release = join(output, 'release');
|
|
3807
|
+
writeFileSync(join(output, 'unrelated'), 'preserved');
|
|
3808
|
+
writeFileSync(join(output, 'target'), 'x'.repeat(128));
|
|
3809
|
+
symlinkSync('./target', join(output, 'alias'));
|
|
3810
|
+
let observedGap = false;
|
|
3811
|
+
const observations = [];
|
|
3812
|
+
let releaseTimer;
|
|
3813
|
+
const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
|
|
3814
|
+
if (String(line).includes('ENOENT'))
|
|
3815
|
+
observations.push(String(line));
|
|
3816
|
+
if (!observedGap && existsSync(ready) && String(line).includes('sampledAggregateRssBytes')) {
|
|
3817
|
+
observedGap = true;
|
|
3818
|
+
// Synchronize real removal with the sample preceding the synchronous filesystem scan.
|
|
3819
|
+
releaseTimer = setTimeout(() => {
|
|
3820
|
+
if (mode === 'cancel')
|
|
3821
|
+
controller.abort();
|
|
3822
|
+
else if (mode !== 'persistent')
|
|
3823
|
+
writeFileSync(release, 'continue');
|
|
3824
|
+
}, 100);
|
|
3825
|
+
}
|
|
3826
|
+
return true;
|
|
3827
|
+
});
|
|
3828
|
+
const script = `
|
|
3829
|
+
(async () => {
|
|
3830
|
+
const fs = require('node:fs');
|
|
3831
|
+
const trace = value => fs.appendFileSync(${JSON.stringify(events)}, value + '\\n');
|
|
3832
|
+
trace('command-started');
|
|
3833
|
+
if (['command-error', 'persistent'].includes(${JSON.stringify(mode)})) console.error('synthetic buffered preparation failure');
|
|
3834
|
+
fs.unlinkSync(${JSON.stringify(join(output, 'target'))});
|
|
3835
|
+
trace('target-removed'); fs.writeFileSync(${JSON.stringify(ready)}, 'ready');
|
|
3836
|
+
if (${JSON.stringify(mode)} === 'limit') fs.writeFileSync(${JSON.stringify(join(output, 'retained'))}, 'x'.repeat(512));
|
|
3837
|
+
while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
3838
|
+
fs.unlinkSync(${JSON.stringify(join(output, 'alias'))}); trace('alias-removed');
|
|
3839
|
+
process.exitCode = ${mode === 'command-error' ? 7 : 0};
|
|
3840
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
3841
|
+
try {
|
|
3842
|
+
const { withRuntimeSetupWriter } = await import('./verification.js');
|
|
3843
|
+
const run = withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
|
|
3844
|
+
maxAggregateRssBytes: 1_000_000_000,
|
|
3845
|
+
minFreeDiskBytes: 1,
|
|
3846
|
+
maxNewOutputBytes: mode === 'limit' ? 256 : 4096,
|
|
3847
|
+
outputRoots: [output],
|
|
3848
|
+
}, controller.signal));
|
|
3849
|
+
if (mode === 'pass')
|
|
3850
|
+
await run;
|
|
3851
|
+
else
|
|
3852
|
+
await assert.rejects(run, (error) => {
|
|
3853
|
+
const message = String(error);
|
|
3854
|
+
assert.match(message, mode === 'cancel'
|
|
3855
|
+
? /cancelled/u
|
|
3856
|
+
: mode === 'persistent'
|
|
3857
|
+
? /remained unavailable.*First observation:.*ENOENT/u
|
|
3858
|
+
: mode === 'limit'
|
|
3859
|
+
? /filesystem output.*exceeded/u
|
|
3860
|
+
: /exit 7/u);
|
|
3861
|
+
assert.doesNotMatch(message, /cleanup failed/u);
|
|
3862
|
+
assert.ok(message.includes(join(output, 'alias')));
|
|
3863
|
+
assert.match(message, /ENOENT/u);
|
|
3864
|
+
if (mode === 'command-error' || mode === 'persistent') {
|
|
3865
|
+
const digest = /Command output (sha256:[a-f0-9]{64})/u.exec(message)?.[1];
|
|
3866
|
+
assert.ok(digest);
|
|
3867
|
+
assert.match(readFileSync(join(root, '.git', 'ai-delivery', 'verification@1', 'command-output', `${digest.slice(7)}.bin`), 'utf8'), /synthetic buffered preparation failure/u);
|
|
3868
|
+
}
|
|
3869
|
+
return true;
|
|
3870
|
+
});
|
|
3871
|
+
assert.ok(observedGap);
|
|
3872
|
+
assert.ok(observations.some((line) => {
|
|
3873
|
+
const observation = JSON.parse(line.slice(line.indexOf('{')));
|
|
3874
|
+
return observation.reason.includes(join(output, 'alias'));
|
|
3875
|
+
}));
|
|
3876
|
+
assert.equal(readFileSync(join(output, 'unrelated'), 'utf8'), 'preserved');
|
|
3877
|
+
console.log(JSON.stringify({
|
|
3878
|
+
scenario: 'ordinary-owned-symlink-teardown',
|
|
3879
|
+
mode,
|
|
3880
|
+
firstObservation: JSON.parse(observations[0].slice(observations[0].indexOf('{')))
|
|
3881
|
+
.reason,
|
|
3882
|
+
events: readFileSync(events, 'utf8').trim().split('\n'),
|
|
3883
|
+
unrelatedPreserved: true,
|
|
3884
|
+
aliasRetained: readdirSync(output).includes('alias'),
|
|
3885
|
+
}));
|
|
3886
|
+
assert.deepEqual(readFileSync(events, 'utf8').trim().split('\n'), mode === 'cancel' || mode === 'persistent'
|
|
3887
|
+
? ['command-started', 'target-removed']
|
|
3888
|
+
: ['command-started', 'target-removed', 'alias-removed']);
|
|
3889
|
+
if (mode === 'cancel' || mode === 'persistent') {
|
|
3890
|
+
await assert.rejects(withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', ''], {
|
|
3891
|
+
maxAggregateRssBytes: 1_000_000_000,
|
|
3892
|
+
minFreeDiskBytes: 1,
|
|
3893
|
+
maxNewOutputBytes: 4096,
|
|
3894
|
+
outputRoots: [output],
|
|
3895
|
+
})), (error) => {
|
|
3896
|
+
assert.ok(String(error).includes(join(output, 'alias')));
|
|
3897
|
+
assert.match(String(error), /ENOENT/u);
|
|
3898
|
+
return true;
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
finally {
|
|
3903
|
+
controller.abort();
|
|
3904
|
+
if (releaseTimer !== undefined)
|
|
3905
|
+
clearTimeout(releaseTimer);
|
|
3906
|
+
progress.mockRestore();
|
|
3907
|
+
rmSync(root, { recursive: true, force: true });
|
|
3908
|
+
rmSync(output, { recursive: true, force: true });
|
|
3909
|
+
}
|
|
3910
|
+
});
|
|
3911
|
+
test('injected observation deadline rejects a late complete real filesystem retry', async () => {
|
|
3912
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-observation-deadline-')));
|
|
3913
|
+
const { root } = await fixture();
|
|
3914
|
+
const target = join(output, 'target'), alias = join(output, 'alias'), release = join(output, 'release');
|
|
3915
|
+
writeFileSync(target, 'target');
|
|
3916
|
+
symlinkSync('./target', alias);
|
|
3917
|
+
const now = Date.now.bind(Date);
|
|
3918
|
+
let offset = 0;
|
|
3919
|
+
let firstObservation;
|
|
3920
|
+
let advance;
|
|
3921
|
+
const clock = vi.spyOn(Date, 'now').mockImplementation(() => now() + offset);
|
|
3922
|
+
const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
|
|
3923
|
+
if (firstObservation === undefined && String(line).includes('ENOENT')) {
|
|
3924
|
+
firstObservation = String(line);
|
|
3925
|
+
advance = setTimeout(() => {
|
|
3926
|
+
// This test injects only the deadline. Alias removal and the subsequent complete scan are real.
|
|
3927
|
+
unlinkSync(alias);
|
|
3928
|
+
offset = 1100;
|
|
3929
|
+
writeFileSync(release, 'finish');
|
|
3930
|
+
}, 0);
|
|
3931
|
+
}
|
|
3932
|
+
return true;
|
|
3933
|
+
});
|
|
3934
|
+
const script = `(async () => {
|
|
3935
|
+
const fs = require('node:fs'); fs.unlinkSync(${JSON.stringify(target)});
|
|
3936
|
+
while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 10));
|
|
3937
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
3938
|
+
try {
|
|
3939
|
+
const { withRuntimeSetupWriter } = await import('./verification.js');
|
|
3940
|
+
await assert.rejects(withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
|
|
3941
|
+
maxAggregateRssBytes: 1_000_000_000,
|
|
3942
|
+
minFreeDiskBytes: 1,
|
|
3943
|
+
maxNewOutputBytes: 4096,
|
|
3944
|
+
outputRoots: [output],
|
|
3945
|
+
})), (error) => {
|
|
3946
|
+
assert.match(String(error), /remained unavailable for 1 second/u);
|
|
3947
|
+
assert.ok(String(error).includes(alias));
|
|
3948
|
+
assert.match(String(error), /ENOENT/u);
|
|
3949
|
+
assert.doesNotMatch(String(error), /cleanup failed/u);
|
|
3950
|
+
return true;
|
|
3951
|
+
});
|
|
3952
|
+
assert.ok(firstObservation);
|
|
3953
|
+
assert.equal(readdirSync(output).includes('alias'), false);
|
|
3954
|
+
}
|
|
3955
|
+
finally {
|
|
3956
|
+
if (advance !== undefined)
|
|
3957
|
+
clearTimeout(advance);
|
|
3958
|
+
progress.mockRestore();
|
|
3959
|
+
clock.mockRestore();
|
|
3960
|
+
rmSync(root, { recursive: true, force: true });
|
|
3961
|
+
rmSync(output, { recursive: true, force: true });
|
|
3962
|
+
}
|
|
3963
|
+
});
|
|
3800
3964
|
test.each(['escape', 'broken', 'cycle'])('output observation rejects %s aliases without running policy commands', async (kind) => {
|
|
3801
3965
|
const { root, counter } = await fixture();
|
|
3802
3966
|
const base = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-output-alias-')));
|