@aviaratech/ai-delivery 0.3.13 → 0.3.14
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
|
@@ -350,7 +350,7 @@ Verification holds one file lease per worktree and durably records its writer an
|
|
|
350
350
|
|
|
351
351
|
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.
|
|
352
352
|
|
|
353
|
-
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.
|
|
353
|
+
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. Unix-domain IPC sockets inside declared roots are observed as endpoints with zero regular-file payload bytes and remain counted toward the scan entry limit. Regular-file growth in every root is still measured; FIFOs and device entries remain unsupported. 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.
|
|
354
354
|
|
|
355
355
|
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.
|
|
356
356
|
|
|
@@ -369,7 +369,7 @@ await withVerificationFilesystemFixture(async () => {
|
|
|
369
369
|
});
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
Bounded verification supplies an inherited, run-owned filesystem coordination capability. The helper serializes the fixture's lifetime against strict scans of every declared root; nested verification and runtime setup join the same boundary. Without an inherited observer the helper runs its callback normally. Malformed, foreign or stale capabilities fail closed. The callback must contain only the negative test and its teardown, and must not start verification while holding the fixture. A queued scan takes priority over new fixtures. RSS, disk, captured-log limits and cancellation continue while the scan waits; waiting observations do not claim a new filesystem measurement. A fixture that fails to release a waiting scan within five seconds is a stalled reader/teardown failure: the command is terminated through the existing identity-checked cleanup, without stealing the live fixture lease. This is an operation-specific observation-stall bound, not a verification runtime limit. Successful commands and fully cached resumes receive fresh strict scans; ordinary or leaked unsupported entries still fail. Abrupt termination can prevent callback teardown; leaked fixtures remain visible and fail subsequent verification. After existing writer recovery confirms command quiescence, only validated abandoned control metadata from that worktree is retired. No output roots,
|
|
372
|
+
Bounded verification supplies an inherited, run-owned filesystem coordination capability. The helper serializes the fixture's lifetime against strict scans of every declared root; nested verification and runtime setup join the same boundary. Without an inherited observer the helper runs its callback normally. Malformed, foreign or stale capabilities fail closed. The callback must contain only the negative test and its teardown, and must not start verification while holding the fixture. A queued scan takes priority over new fixtures. RSS, disk, captured-log limits and cancellation continue while the scan waits; waiting observations do not claim a new filesystem measurement. A fixture that fails to release a waiting scan within five seconds is a stalled reader/teardown failure: the command is terminated through the existing identity-checked cleanup, without stealing the live fixture lease. This is an operation-specific observation-stall bound, not a verification runtime limit. Successful commands and fully cached resumes receive fresh strict scans; ordinary or leaked unsupported entries still fail. Abrupt termination can prevent callback teardown; leaked fixtures remain visible and fail subsequent verification. After existing writer recovery confirms command quiescence, only validated abandoned control metadata from that worktree is retired. No output roots, unsupported file kinds or regular-file byte growth are exempted.
|
|
373
373
|
|
|
374
374
|
Bounded stage checkpoints bind the limits and sampled observations. Older unmeasured stages and stages with different limits rerun. Current manifests use `ai-delivery.run@3`, bind the worktree and producer, and live under `runs@2/<worktree-digest>/<head>.json`. Bounded manifests also record limits, sample count, and sampled RSS, output, and disk extrema. Historical run versions 1 and 2 remain historical and are available for merged-issue recovery; current publication requires current-producer verification. `processCoverage: "observed-processes-only"` means a passing aggregate is not proof that every detached descendant was drained. A descendant that detaches and closes inherited pipes between samples can evade observation; a pipe holder that prevents command closure fails with unverified cleanup and no checkpoint. Output roots must be canonical and nonoverlapping. Resolvable aliases such as npm `.bin` links may point only within the declared roots; aliases are not traversed or counted twice, and growth of their physical targets remains measured. Escaping, broken and cyclic links fail closed. Output baselines are retained across retries, including failed stages. They are range scoped: a changed baseline regenerates the measured stage proof. Consumers must confirm their command graph remains observable and declare all relevant output roots; this contract does not assert ownership of undeclared output locations or other concurrent writers.
|
|
375
375
|
|
package/dist/lifecycle.test.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { syncBuiltinESMExports } from 'node:module';
|
|
5
5
|
import { cpSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, utimesSync, writeFileSync, } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
|
+
import { createServer } from 'node:net';
|
|
7
8
|
import { dirname, join } from 'node:path';
|
|
8
9
|
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { afterAll, test, vi } from 'vitest';
|
|
@@ -4701,6 +4702,125 @@ const child = require('node:child_process').spawnSync(${JSON.stringify(consumer.
|
|
|
4701
4702
|
if (child.error) throw child.error;
|
|
4702
4703
|
process.exit(child.status ?? 1);`;
|
|
4703
4704
|
}
|
|
4705
|
+
test.each([
|
|
4706
|
+
['pass', 'node24'],
|
|
4707
|
+
['limit', 'node24'],
|
|
4708
|
+
['cancel', 'node24'],
|
|
4709
|
+
['error', 'node24'],
|
|
4710
|
+
['pass', 'node26'],
|
|
4711
|
+
['limit', 'node26'],
|
|
4712
|
+
['cancel', 'node26'],
|
|
4713
|
+
['error', 'node26'],
|
|
4714
|
+
])('Unix IPC sockets preserve regular-file output and %s verification on %s', async (mode, runtime) => {
|
|
4715
|
+
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ad-socket-')));
|
|
4716
|
+
const baselineSocket = join(output, 'baseline.sock'), childSocket = join(output, 'child.sock');
|
|
4717
|
+
const artifact = join(output, 'artifact'), unrelated = join(output, 'unrelated');
|
|
4718
|
+
writeFileSync(artifact, 'x'.repeat(512));
|
|
4719
|
+
writeFileSync(unrelated, 'preserved');
|
|
4720
|
+
const server = createServer((socket) => {
|
|
4721
|
+
socket.destroy();
|
|
4722
|
+
});
|
|
4723
|
+
let root;
|
|
4724
|
+
try {
|
|
4725
|
+
await new Promise((resolve, reject) => {
|
|
4726
|
+
server.once('error', reject);
|
|
4727
|
+
server.listen(baselineSocket, resolve);
|
|
4728
|
+
});
|
|
4729
|
+
const script = `(async () => {
|
|
4730
|
+
const fs = require('node:fs'), net = require('node:net');
|
|
4731
|
+
await new Promise((resolve, reject) => {
|
|
4732
|
+
const socket = net.connect(${JSON.stringify(baselineSocket)});
|
|
4733
|
+
socket.once('error', reject); socket.once('connect', () => { socket.destroy(); resolve(); });
|
|
4734
|
+
});
|
|
4735
|
+
const server = net.createServer();
|
|
4736
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(${JSON.stringify(childSocket)}, resolve); });
|
|
4737
|
+
fs.writeFileSync(${JSON.stringify(join(output, 'socket-ready'))}, 'ready');
|
|
4738
|
+
try {
|
|
4739
|
+
for (let unit = 0; unit < 12; unit++) {
|
|
4740
|
+
fs.appendFileSync(${JSON.stringify(artifact)}, 'x'.repeat(256));
|
|
4741
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4742
|
+
}
|
|
4743
|
+
} finally { await new Promise(resolve => server.close(resolve)); }
|
|
4744
|
+
if (${JSON.stringify(mode)} === 'error') process.exitCode = 9;
|
|
4745
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`;
|
|
4746
|
+
({ root } = await fixture({ personalAuthor: true, firstStageScript: script }));
|
|
4747
|
+
const row = await prepareIssueWorktree({
|
|
4748
|
+
baseRef: 'main',
|
|
4749
|
+
identity: 'host-author',
|
|
4750
|
+
issueNumber: 17,
|
|
4751
|
+
repoRoot: root,
|
|
4752
|
+
});
|
|
4753
|
+
const clients = syntheticDiscoveryClients();
|
|
4754
|
+
const replies = [];
|
|
4755
|
+
for (const fragment of [
|
|
4756
|
+
'query DeliveryRepository',
|
|
4757
|
+
'query ProjectDeliveryConfiguration',
|
|
4758
|
+
'issueFields(first:',
|
|
4759
|
+
'issueTypes(first:',
|
|
4760
|
+
])
|
|
4761
|
+
replies.push({ fragment, data: await clients.graphql(fragment) });
|
|
4762
|
+
const consumer = runtime === 'node26'
|
|
4763
|
+
? packedNode26Consumer()
|
|
4764
|
+
: { executable: process.execPath, root: fileURLToPath(new URL('../', import.meta.url)) };
|
|
4765
|
+
const result = execFileSync(consumer.executable, [
|
|
4766
|
+
'-e',
|
|
4767
|
+
`(async () => {
|
|
4768
|
+
const assert = require('node:assert/strict'), fs = require('node:fs');
|
|
4769
|
+
assert.equal(process.version, ${JSON.stringify(runtime === 'node26' ? 'v26.2.0' : 'v24.21.0')});
|
|
4770
|
+
const replies = ${JSON.stringify(replies)};
|
|
4771
|
+
globalThis.fetch = async (url, options) => {
|
|
4772
|
+
assert.equal(String(url), 'https://api.github.com/graphql');
|
|
4773
|
+
const reply = replies.find(reply => JSON.parse(options.body).query.includes(reply.fragment));
|
|
4774
|
+
assert.ok(reply); return new Response(JSON.stringify({ data: reply.data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
4775
|
+
};
|
|
4776
|
+
process.env.AUTHOR_TOKEN = 'synthetic-author-token';
|
|
4777
|
+
const { verifyIssue } = await import(${JSON.stringify(runtime === 'node26' ? '@aviaratech/ai-delivery/agent' : new URL('./agent.js', import.meta.url).href)});
|
|
4778
|
+
const controller = new AbortController(), observations = [];
|
|
4779
|
+
const cancellation = ${JSON.stringify(mode)} === 'cancel' ? setInterval(() => {
|
|
4780
|
+
if (fs.existsSync(${JSON.stringify(childSocket)})) {
|
|
4781
|
+
assert.equal(fs.lstatSync(${JSON.stringify(childSocket)}).isSocket(), true);
|
|
4782
|
+
controller.abort();
|
|
4783
|
+
}
|
|
4784
|
+
}, 10) : undefined;
|
|
4785
|
+
const originalWrite = process.stderr.write;
|
|
4786
|
+
process.stderr.write = line => {
|
|
4787
|
+
observations.push(String(line));
|
|
4788
|
+
return true;
|
|
4789
|
+
};
|
|
4790
|
+
try {
|
|
4791
|
+
const running = verifyIssue({ issueNumber: 17, repoRoot: ${JSON.stringify(row.path)}, signal: controller.signal,
|
|
4792
|
+
resourceBounds: { maxAggregateRssBytes: 1000000000, minFreeDiskBytes: 1,
|
|
4793
|
+
maxNewOutputBytes: ${mode === 'limit' ? 1024 : 8192}, outputRoots: [${JSON.stringify(output)}] } });
|
|
4794
|
+
if (${JSON.stringify(mode)} === 'pass') {
|
|
4795
|
+
const run = await running;
|
|
4796
|
+
assert.ok(run.resources.sampleCount > 0);
|
|
4797
|
+
assert.ok(run.resources.maxSampledNewOutputBytes >= 3072);
|
|
4798
|
+
assert.equal(fs.statSync(${JSON.stringify(artifact)}).size, 3584);
|
|
4799
|
+
assert.equal(fs.existsSync(${JSON.stringify(childSocket)}), false);
|
|
4800
|
+
assert.equal(fs.lstatSync(${JSON.stringify(baselineSocket)}).isSocket(), true);
|
|
4801
|
+
} else await assert.rejects(running, error => {
|
|
4802
|
+
assert.match(String(error), ${mode === 'limit' ? '/filesystem output.*exceeded/' : mode === 'cancel' ? '/cancelled/' : '/exit 9/'});
|
|
4803
|
+
assert.doesNotMatch(String(error), /cleanup failed/); return true;
|
|
4804
|
+
});
|
|
4805
|
+
assert.equal(fs.readFileSync(${JSON.stringify(unrelated)}, 'utf8'), 'preserved');
|
|
4806
|
+
console.log(JSON.stringify({ scenario: 'unix-ipc-socket', runtime: process.version, mode: ${JSON.stringify(mode)},
|
|
4807
|
+
sampleCount: observations.length, unrelatedPreserved: true }));
|
|
4808
|
+
} finally { clearInterval(cancellation); controller.abort(); process.stderr.write = originalWrite; }
|
|
4809
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });`,
|
|
4810
|
+
], { cwd: consumer.root, encoding: 'utf8', maxBuffer: 1024 * 1024 });
|
|
4811
|
+
console.log(result.trim());
|
|
4812
|
+
assert.ok(existsSync(join(output, 'socket-ready')));
|
|
4813
|
+
}
|
|
4814
|
+
finally {
|
|
4815
|
+
if (server.listening)
|
|
4816
|
+
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
4817
|
+
assert.equal(existsSync(baselineSocket), false);
|
|
4818
|
+
if (root !== undefined)
|
|
4819
|
+
rmSync(root, { recursive: true, force: true });
|
|
4820
|
+
rmSync(output, { recursive: true, force: true });
|
|
4821
|
+
assert.equal(existsSync(output), false);
|
|
4822
|
+
}
|
|
4823
|
+
});
|
|
4704
4824
|
test.each(['pass', 'absent', 'limit', 'cancel'])('Node 26 packed public verification preserves CLI rebuild %s behavior', async (mode) => {
|
|
4705
4825
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-packed-rebuild-')));
|
|
4706
4826
|
const target = join(output, 'cli.js');
|