@aviaratech/ai-delivery 0.3.12 → 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 +18 -2
- package/dist/issue.js +36 -7
- package/dist/issue.js.map +1 -1
- package/dist/lifecycle.test.js +314 -46
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/pr.js +22 -4
- package/dist/pr.js.map +1 -1
- package/dist/services/worktreeRegistry.d.ts +2 -2
- package/dist/services/worktreeRegistry.js +2 -4
- package/dist/services/worktreeRegistry.js.map +1 -1
- package/dist/verification.js +6 -2
- package/dist/verification.js.map +1 -1
- package/dist/worktree.d.ts +2 -0
- package/dist/worktree.js +18 -9
- package/dist/worktree.js.map +1 -1
- package/dist/worktreeTransition.test.js +5 -5
- package/dist/worktreeTransition.test.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ai-delivery/.claude-plugin/plugin.json +1 -1
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';
|
|
@@ -21,7 +22,7 @@ import * as githubClient from './github/client.js';
|
|
|
21
22
|
import * as atomicJson from './utils/atomicJson.js';
|
|
22
23
|
import { createIssue, developIssue, listIssueSubissues, readyCheck, resumeCreatedIssue, updateIssue, } from './issue.js';
|
|
23
24
|
import { checkoutPr, finishIssue, listPrs, mergePr, prChecks, prInfo, publishPr, submitFormalReview } from './pr.js';
|
|
24
|
-
import { addWorktreeEntry, removeWorktreeEntry, updateIssueWorktreeDelivery } from './services/worktreeRegistry.js';
|
|
25
|
+
import { addWorktreeEntry, assertNativeIssueTrackingAdmission, getIssueWorktreeStrict, removeWorktreeEntry, updateIssueWorktreeDelivery, } from './services/worktreeRegistry.js';
|
|
25
26
|
import { assertDeliveryRuntimeAdmitted } from './services/deliveryAdmission.js';
|
|
26
27
|
import { getDeliveryRecords } from './services/deliveryRecordService.js';
|
|
27
28
|
import { createIssuePhaseEvidence, loadVerifiedRun, verifyIssue } from './verification.js';
|
|
@@ -540,38 +541,13 @@ test('an old active issue remains pinned while a new row uses the same canonical
|
|
|
540
541
|
assert.equal(readFileSync(registryPath, 'utf8'), futureBytes);
|
|
541
542
|
assert.equal(existsSync(join(root, '.worktrees', 'scratch-new')), false);
|
|
542
543
|
writeFileSync(registryPath, JSON.stringify({ worktrees: [old] }));
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
issues: {
|
|
551
|
-
get: async () => ({ data: { id: 17, number: 17, node_id: 'PARENT-17', state: 'open' } }),
|
|
552
|
-
create: async () => {
|
|
553
|
-
issueWrites += 1;
|
|
554
|
-
return { data: {} };
|
|
555
|
-
},
|
|
556
|
-
update: async () => {
|
|
557
|
-
issueWrites += 1;
|
|
558
|
-
return { data: {} };
|
|
559
|
-
},
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
graphql: async () => ({
|
|
563
|
-
repository: {
|
|
564
|
-
issue: {
|
|
565
|
-
parent: { id: 'PARENT-17', number: 17 },
|
|
566
|
-
blockedBy: { nodes: [], pageInfo: { endCursor: null, hasNextPage: false } },
|
|
567
|
-
},
|
|
568
|
-
},
|
|
569
|
-
}),
|
|
570
|
-
},
|
|
571
|
-
};
|
|
572
|
-
await assert.rejects(createIssue(relationshipContext, { title: 'Synthetic child', parentIssueNumber: 17 }), /ownership witness/u);
|
|
573
|
-
await assert.rejects(updateIssue(relationshipContext, { issueNumber: 17, parentIssueNumber: null }), /ownership witness/u);
|
|
574
|
-
assert.equal(issueWrites, 0);
|
|
544
|
+
assert.doesNotThrow(() => assertNativeIssueTrackingAdmission(17, root));
|
|
545
|
+
const original = readFileSync(registryPath, 'utf8');
|
|
546
|
+
writeFileSync(registryPath, JSON.stringify({ worktrees: [old, old] }));
|
|
547
|
+
assert.throws(() => assertNativeIssueTrackingAdmission(17, root), /duplicate/u);
|
|
548
|
+
writeFileSync(registryPath, futureBytes);
|
|
549
|
+
assert.throws(() => assertNativeIssueTrackingAdmission(17, root), /unsupported fields/u);
|
|
550
|
+
writeFileSync(registryPath, original);
|
|
575
551
|
await assert.rejects(prepareIssueWorktree({ identity: 'synthetic-author', issueNumber: 17, repoRoot: root }), /ownership witness/u);
|
|
576
552
|
const row = await prepareStandaloneWorktree({
|
|
577
553
|
branch: 'scratch/new',
|
|
@@ -733,8 +709,7 @@ test.each(['current-blocker', 'new-blocker', 'current-parent', 'new-parent'])('t
|
|
|
733
709
|
await assert.rejects(updateIssue(context, input), /invalid blocked-by relationship evidence/u);
|
|
734
710
|
assert.equal(writes, 0);
|
|
735
711
|
malformedRelationships = false;
|
|
736
|
-
|
|
737
|
-
assert.equal(writes, 0);
|
|
712
|
+
assert.throws(() => getIssueWorktreeStrict(17, root), /ownership witness/u);
|
|
738
713
|
writeFileSync(registryPath, JSON.stringify({ worktrees: [{ ...legacy, schemaVersion: 'unknown@99' }] }));
|
|
739
714
|
await assert.rejects(updateIssue(context, input), /unsupported fields/u);
|
|
740
715
|
assert.equal(writes, 0);
|
|
@@ -3618,7 +3593,10 @@ async function syntheticLifecycle(routing) {
|
|
|
3618
3593
|
},
|
|
3619
3594
|
}),
|
|
3620
3595
|
},
|
|
3621
|
-
repos: {
|
|
3596
|
+
repos: {
|
|
3597
|
+
get: async () => ({ data: { full_name: 'example/widget' } }),
|
|
3598
|
+
getCombinedStatusForRef: async () => ({ data: { statuses: [], state: 'success' } }),
|
|
3599
|
+
},
|
|
3622
3600
|
};
|
|
3623
3601
|
let authenticatedAuthor = 'synthetic-author[bot]';
|
|
3624
3602
|
const context = {
|
|
@@ -3638,7 +3616,71 @@ async function syntheticLifecycle(routing) {
|
|
|
3638
3616
|
},
|
|
3639
3617
|
};
|
|
3640
3618
|
let restoreDispatchClient;
|
|
3619
|
+
const enableDispatch = () => {
|
|
3620
|
+
const dispatchClient = vi.spyOn(githubClient, 'createDeliveryGitHubClients').mockImplementation(async (input) => ({
|
|
3621
|
+
...context.clients,
|
|
3622
|
+
...(input.role === 'reviewer'
|
|
3623
|
+
? {
|
|
3624
|
+
role: 'reviewer',
|
|
3625
|
+
appActorLogin: async () => 'synthetic-reviewer[bot]',
|
|
3626
|
+
}
|
|
3627
|
+
: {}),
|
|
3628
|
+
graphql: (async (query, variables) => query.includes('DeliveryDiscovery') ||
|
|
3629
|
+
query.includes('DeliveryRepository') ||
|
|
3630
|
+
query.includes('ProjectDeliveryConfiguration')
|
|
3631
|
+
? syntheticDiscoveryClients().graphql(query, variables)
|
|
3632
|
+
: graphql(query, variables)),
|
|
3633
|
+
}));
|
|
3634
|
+
restoreDispatchClient = () => dispatchClient.mockRestore();
|
|
3635
|
+
return dispatchClient;
|
|
3636
|
+
};
|
|
3637
|
+
const execution = { repoRoot: root, runtimeEntryPath, identity: 'synthetic-author' };
|
|
3641
3638
|
try {
|
|
3639
|
+
if (routing.legacyNativeMetadata) {
|
|
3640
|
+
enableDispatch();
|
|
3641
|
+
const legacyPath = join(root, '.worktrees', 'issue-17');
|
|
3642
|
+
git(root, 'worktree', 'add', '-b', 'issue/17', legacyPath, 'main');
|
|
3643
|
+
writeFileSync(join(legacyPath, 'artifact.txt'), 'unadopted legacy source\n');
|
|
3644
|
+
const receiptRoot = join(root, '.git', 'legacy-receipts');
|
|
3645
|
+
mkdirSync(receiptRoot);
|
|
3646
|
+
writeFileSync(join(receiptRoot, 'run.json'), '{"producer":"synthetic-legacy"}\n');
|
|
3647
|
+
const registryPath = join(root, '.issue-cli', 'worktrees.json');
|
|
3648
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
3649
|
+
const legacy = {
|
|
3650
|
+
branch: 'issue/17',
|
|
3651
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3652
|
+
identity: 'synthetic-legacy',
|
|
3653
|
+
issueNumber,
|
|
3654
|
+
path: legacyPath,
|
|
3655
|
+
status: 'active',
|
|
3656
|
+
type: 'issue',
|
|
3657
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
3658
|
+
};
|
|
3659
|
+
const registryBytes = JSON.stringify({ worktrees: [legacy] });
|
|
3660
|
+
writeFileSync(registryPath, registryBytes);
|
|
3661
|
+
const source = directoryHash(legacyPath);
|
|
3662
|
+
const receipts = directoryHash(receiptRoot);
|
|
3663
|
+
await assert.rejects(executeTool('issue_update', { issueNumber, points: 2 }, {
|
|
3664
|
+
...execution,
|
|
3665
|
+
identity: 'synthetic-reviewer',
|
|
3666
|
+
}), /identity|author|permitted|allowed/u);
|
|
3667
|
+
const updated = (await executeTool('issue_update', {
|
|
3668
|
+
issueNumber,
|
|
3669
|
+
parentIssueNumber: 11,
|
|
3670
|
+
points: 2,
|
|
3671
|
+
priority: 'High',
|
|
3672
|
+
}, execution));
|
|
3673
|
+
assert.equal(updated.points, 2);
|
|
3674
|
+
assert.equal(updated.priority, 'High');
|
|
3675
|
+
assert.equal(parentIssueNumber, 11);
|
|
3676
|
+
assert.equal(parentPoints, undefined);
|
|
3677
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /ownership witness/u);
|
|
3678
|
+
assert.equal(readFileSync(registryPath, 'utf8'), registryBytes);
|
|
3679
|
+
assert.equal(directoryHash(legacyPath), source);
|
|
3680
|
+
assert.equal(directoryHash(receiptRoot), receipts);
|
|
3681
|
+
assert.equal(existsSync(join(gitCommonDir(root), 'ai-delivery/worktree-owners')), false);
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3642
3684
|
if (routing.producerOnboarding) {
|
|
3643
3685
|
assert.equal(existsSync(join(root, 'AGENTS.md')), false);
|
|
3644
3686
|
const archiveBase = join(root, '.git', 'producer-fixture');
|
|
@@ -3942,21 +3984,126 @@ exec "${realGit}" "$@"
|
|
|
3942
3984
|
await assert.rejects(finishIssue(context, { issueNumber, prNumber, strategy: 'merge' }), /exact prepared merge intent/u);
|
|
3943
3985
|
headSha = reviewedHead;
|
|
3944
3986
|
assert.equal(calls.filter((call) => call === 'git:createCommit').length, 1);
|
|
3987
|
+
if (routing.mergedContinuation) {
|
|
3988
|
+
enableDispatch();
|
|
3989
|
+
const terminal = await executeTool('issue_pr_merge', { issueNumber, prNumber, strategy: 'merge' }, execution);
|
|
3990
|
+
assert.equal(terminal.mergeSha, mergeSha);
|
|
3991
|
+
assert.equal(issue.state, 'open');
|
|
3992
|
+
const previous = getIssueWorktreeStrict(issueNumber, root);
|
|
3993
|
+
assert.equal(previous.status, 'merged');
|
|
3994
|
+
const registryPath = join(root, '.issue-cli', 'worktrees.json');
|
|
3995
|
+
const registryBytes = readFileSync(registryPath);
|
|
3996
|
+
const custody = [
|
|
3997
|
+
'runs@2',
|
|
3998
|
+
'publications',
|
|
3999
|
+
'reviews',
|
|
4000
|
+
'merge-intents',
|
|
4001
|
+
'merge-attempts',
|
|
4002
|
+
'merge-results',
|
|
4003
|
+
'merges',
|
|
4004
|
+
];
|
|
4005
|
+
const oldReceipts = [];
|
|
4006
|
+
const saveReceipts = (path) => {
|
|
4007
|
+
if (!existsSync(path))
|
|
4008
|
+
return;
|
|
4009
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
4010
|
+
const child = join(path, entry.name);
|
|
4011
|
+
if (entry.isDirectory())
|
|
4012
|
+
saveReceipts(child);
|
|
4013
|
+
else
|
|
4014
|
+
oldReceipts.push({ path: child, bytes: readFileSync(child) });
|
|
4015
|
+
}
|
|
4016
|
+
};
|
|
4017
|
+
for (const name of custody)
|
|
4018
|
+
saveReceipts(join(gitCommonDir(root), 'ai-delivery', name));
|
|
4019
|
+
const ownerPath = join(gitCommonDir(root), 'ai-delivery/worktree-owners');
|
|
4020
|
+
const ownerHash = directoryHash(ownerPath);
|
|
4021
|
+
const publicationPath = join(gitCommonDir(root), 'ai-delivery/publications', String(issueNumber), `${headSha}.json`);
|
|
4022
|
+
const publicationBytes = readFileSync(publicationPath);
|
|
4023
|
+
const { publicationId: _publicationId, ...publicationContent } = JSON.parse(publicationBytes.toString());
|
|
4024
|
+
for (const stale of [
|
|
4025
|
+
{ issueNumber: 18 },
|
|
4026
|
+
{ headSha: 'c'.repeat(40) },
|
|
4027
|
+
{ baseSha: 'c'.repeat(40) },
|
|
4028
|
+
{ evidenceId: `sha256:${'c'.repeat(64)}` },
|
|
4029
|
+
]) {
|
|
4030
|
+
const content = { ...publicationContent, ...stale };
|
|
4031
|
+
const bytes = Buffer.from(JSON.stringify({ ...content, publicationId: digestValue(content) }));
|
|
4032
|
+
writeFileSync(publicationPath, bytes, { mode: 0o600 });
|
|
4033
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /publication.*(?:verified coordinates|historical approved review)/u);
|
|
4034
|
+
assert.deepEqual(readFileSync(registryPath), registryBytes);
|
|
4035
|
+
assert.deepEqual(readFileSync(publicationPath), bytes);
|
|
4036
|
+
}
|
|
4037
|
+
writeFileSync(publicationPath, publicationBytes, { mode: 0o600 });
|
|
4038
|
+
const reviewPath = join(gitCommonDir(root), 'ai-delivery/reviews', String(issueNumber), `${headSha}.json`);
|
|
4039
|
+
const reviewBytes = readFileSync(reviewPath);
|
|
4040
|
+
const { receiptId: _receiptId, artifact: savedArtifact, ...reviewContent } = JSON.parse(reviewBytes.toString());
|
|
4041
|
+
const { artifactId: _artifactId, ...artifactContent } = savedArtifact;
|
|
4042
|
+
const staleArtifact = { ...artifactContent, diffScopeHash: `sha256:${'c'.repeat(64)}` };
|
|
4043
|
+
const staleReview = { ...reviewContent, artifact: { ...staleArtifact, artifactId: digestValue(staleArtifact) } };
|
|
4044
|
+
const staleReviewBytes = Buffer.from(JSON.stringify({ ...staleReview, receiptId: digestValue(staleReview) }));
|
|
4045
|
+
writeFileSync(reviewPath, staleReviewBytes, { mode: 0o600 });
|
|
4046
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /exact historical approved review/u);
|
|
4047
|
+
assert.deepEqual(readFileSync(registryPath), registryBytes);
|
|
4048
|
+
assert.deepEqual(readFileSync(reviewPath), staleReviewBytes);
|
|
4049
|
+
writeFileSync(reviewPath, reviewBytes, { mode: 0o600 });
|
|
4050
|
+
await assert.rejects(developIssue({
|
|
4051
|
+
...context,
|
|
4052
|
+
config: {
|
|
4053
|
+
...config,
|
|
4054
|
+
roles: {
|
|
4055
|
+
...config.roles,
|
|
4056
|
+
author: { ...config.roles.author, identity: 'other-owner' },
|
|
4057
|
+
},
|
|
4058
|
+
},
|
|
4059
|
+
}, issueNumber), /same preparing owner/u);
|
|
4060
|
+
issue.state = 'closed';
|
|
4061
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /closed/u);
|
|
4062
|
+
issue.state = 'open';
|
|
4063
|
+
headSha = 'b'.repeat(40);
|
|
4064
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /terminal receipt/u);
|
|
4065
|
+
headSha = reviewedHead;
|
|
4066
|
+
const mergePath = join(gitCommonDir(root), 'ai-delivery/merges', String(issueNumber), `${headSha}.json`);
|
|
4067
|
+
const mergeBytes = readFileSync(mergePath);
|
|
4068
|
+
unlinkSync(mergePath);
|
|
4069
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /exact terminal merge receipt/u);
|
|
4070
|
+
writeFileSync(mergePath, mergeBytes, { mode: 0o600 });
|
|
4071
|
+
assert.deepEqual(readFileSync(registryPath), registryBytes);
|
|
4072
|
+
projectStatus = null;
|
|
4073
|
+
failNextProjectSync = true;
|
|
4074
|
+
await assert.rejects(executeTool('issue_develop', { issueNumber }, execution), /synthetic Project interruption/u);
|
|
4075
|
+
const reactivated = getIssueWorktreeStrict(issueNumber, root);
|
|
4076
|
+
assert.equal(reactivated.status, 'active');
|
|
4077
|
+
assert.equal(reactivated.prNumber, undefined);
|
|
4078
|
+
assert.equal(reactivated.identity, previous.identity);
|
|
4079
|
+
assert.equal(reactivated.createdAt, previous.createdAt);
|
|
4080
|
+
const resumed = await executeTool('issue_develop', { issueNumber }, execution);
|
|
4081
|
+
assert.deepEqual(resumed, { path: previous.path, branch: previous.branch });
|
|
4082
|
+
assert.equal(projectStatus, 'Active');
|
|
4083
|
+
await assert.rejects(verifyIssue({ issueNumber, repoRoot: row.path }), /already-merged issue head/u);
|
|
4084
|
+
for (const saved of oldReceipts) {
|
|
4085
|
+
assert.deepEqual(readFileSync(saved.path), saved.bytes);
|
|
4086
|
+
}
|
|
4087
|
+
assert.equal(directoryHash(ownerPath), ownerHash);
|
|
4088
|
+
// A fresh commit produces new verification slots while the complete prior head remains immutable.
|
|
4089
|
+
writeFileSync(join(row.path, 'change.txt'), 'continued feature\n');
|
|
4090
|
+
git(row.path, 'add', 'change.txt');
|
|
4091
|
+
git(row.path, 'commit', '-qm', 'continue unfinished synthetic issue');
|
|
4092
|
+
const continued = await verifyIssue({ issueNumber, repoRoot: row.path });
|
|
4093
|
+
assert.notEqual(continued.classification.head.sha, reviewedHead);
|
|
4094
|
+
for (const saved of oldReceipts)
|
|
4095
|
+
assert.deepEqual(readFileSync(saved.path), saved.bytes);
|
|
4096
|
+
assert.equal(directoryHash(ownerPath), ownerHash);
|
|
4097
|
+
assert.equal(calls.filter((call) => call === 'git:createCommit').length, 1);
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
3945
4100
|
const blockedRecordPath = join(gitCommonDir(root), 'ai-delivery', 'deliveries.json');
|
|
3946
4101
|
mkdirSync(blockedRecordPath);
|
|
3947
4102
|
await assert.rejects(finishIssue(context, { issueNumber, prNumber, strategy: 'merge' }), /Delivery record store is invalid/u);
|
|
3948
4103
|
assert.equal(existsSync(row.path), false);
|
|
3949
4104
|
rmSync(blockedRecordPath, { recursive: true });
|
|
3950
|
-
const dispatchClient =
|
|
3951
|
-
|
|
3952
|
-
graphql: (async (query, variables) => query.includes('DeliveryDiscovery') ||
|
|
3953
|
-
query.includes('DeliveryRepository') ||
|
|
3954
|
-
query.includes('ProjectDeliveryConfiguration')
|
|
3955
|
-
? syntheticDiscoveryClients().graphql(query, variables)
|
|
3956
|
-
: graphql(query, variables)),
|
|
3957
|
-
}));
|
|
3958
|
-
restoreDispatchClient = () => dispatchClient.mockRestore();
|
|
3959
|
-
const execution = { repoRoot: root, runtimeEntryPath, identity: 'synthetic-author' };
|
|
4105
|
+
const dispatchClient = enableDispatch();
|
|
4106
|
+
execution.runtimeEntryPath = runtimeEntryPath;
|
|
3960
4107
|
const terminalMerge = (await executeTool('issue_pr_merge', { issueNumber, prNumber, strategy: 'merge' }, execution));
|
|
3961
4108
|
assert.equal(terminalMerge.mergeSha, git(remote, 'rev-parse', 'refs/heads/main'));
|
|
3962
4109
|
const finished = (await executeTool('issue_finish', { issueNumber, prNumber, strategy: 'merge' }, execution));
|
|
@@ -4020,6 +4167,8 @@ test.each([
|
|
|
4020
4167
|
{ remote: 'upstream', divergentOrigin: false },
|
|
4021
4168
|
{ remote: 'upstream', divergentOrigin: true },
|
|
4022
4169
|
])('synthetic lifecycle finishes with remote $remote and divergent origin $divergentOrigin', syntheticLifecycle, 15_000);
|
|
4170
|
+
test('supported develop resumes an open merged issue under the same owner and preserves prior receipts', { timeout: 0 }, async () => syntheticLifecycle({ remote: 'origin', divergentOrigin: false, mergedContinuation: true }));
|
|
4171
|
+
test('supported native metadata updates a legacy target without adopting its source or receipts', { timeout: 0 }, async () => syntheticLifecycle({ remote: 'origin', divergentOrigin: false, legacyNativeMetadata: true }));
|
|
4023
4172
|
test('an unrelated consumer uses public MCP setup through verified configured-author publication, counted review readback, merge and cleanup', { timeout: 0 }, async () => {
|
|
4024
4173
|
await syntheticLifecycle({ remote: 'origin', divergentOrigin: false, producerOnboarding: true });
|
|
4025
4174
|
});
|
|
@@ -4553,6 +4702,125 @@ const child = require('node:child_process').spawnSync(${JSON.stringify(consumer.
|
|
|
4553
4702
|
if (child.error) throw child.error;
|
|
4554
4703
|
process.exit(child.status ?? 1);`;
|
|
4555
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
|
+
});
|
|
4556
4824
|
test.each(['pass', 'absent', 'limit', 'cancel'])('Node 26 packed public verification preserves CLI rebuild %s behavior', async (mode) => {
|
|
4557
4825
|
const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-packed-rebuild-')));
|
|
4558
4826
|
const target = join(output, 'cli.js');
|