@aviaratech/ai-delivery 0.3.16 → 0.3.18

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.
@@ -15,6 +15,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
15
15
  import { AI_DELIVERY_MCP_TOOLS } from './mcp/tools.js';
16
16
  import { createAiDeliveryMcpServer } from './mcp/index.js';
17
17
  import { loadDeliveryConfig, parseDeliveryConfig } from './config/deliveryConfig.js';
18
+ import * as deliveryConfiguration from './config/deliveryConfig.js';
18
19
  import { digestValue, stableJson } from './delivery/index.js';
19
20
  import { contextFor, executeTool, resumedIssueUpdate, startTrackedIssue } from './dispatch.js';
20
21
  import { defaultBaseRef, gitCommonDir } from './git.js';
@@ -23,7 +24,7 @@ import * as atomicJson from './utils/atomicJson.js';
23
24
  import { createIssue, developIssue, listIssueSubissues, readyCheck, resumeCreatedIssue, updateIssue, } from './issue.js';
24
25
  import { checkoutPr, finishIssue, listPrs, mergePr, prChecks, prInfo, publishPr, submitFormalReview } from './pr.js';
25
26
  import { addWorktreeEntry, assertNativeIssueTrackingAdmission, getIssueWorktreeStrict, removeWorktreeEntry, updateIssueWorktreeDelivery, } from './services/worktreeRegistry.js';
26
- import { assertDeliveryRuntimeAdmitted } from './services/deliveryAdmission.js';
27
+ import { assertDeliveryRuntimeAdmitted, buildRuntimeAdmission, } from './services/deliveryAdmission.js';
27
28
  import { getDeliveryRecords } from './services/deliveryRecordService.js';
28
29
  import { createIssuePhaseEvidence, loadVerifiedRun, verifyIssue } from './verification.js';
29
30
  import { cleanupNonIssueWorktree, prepareIssueWorktree, preparePrWorktree, prepareStandaloneWorktree, } from './worktree.js';
@@ -369,6 +370,1174 @@ export default {
369
370
  throw error;
370
371
  }
371
372
  }
373
+ async function sourcePhaseFixture(options = {}) {
374
+ const fixtureResult = await fixture(options);
375
+ const { root } = fixtureResult;
376
+ const identity = (await loadDeliveryConfig(root)).config.roles.author.identity;
377
+ const sdkRoot = dirname(dirname(fileURLToPath(import.meta.url)));
378
+ const runtimeEntryPath = join(sdkRoot, 'dist', 'cli.js');
379
+ const packageVersion = JSON.parse(readFileSync(join(sdkRoot, 'package.json'), 'utf8'))
380
+ .version;
381
+ const selectedAdmission = buildRuntimeAdmission({
382
+ cliPath: runtimeEntryPath,
383
+ mcpLauncherPath: join(sdkRoot, 'plugins', 'ai-delivery', 'dist', 'mcp-launcher.js'),
384
+ pluginManifestPath: join(sdkRoot, 'plugins', 'ai-delivery', '.claude-plugin', 'plugin.json'),
385
+ packageVersion,
386
+ sourceArchiveSha256: digestValue('synthetic source-phase archive'),
387
+ sourceCommit: git(sdkRoot, 'rev-parse', 'HEAD'),
388
+ configuration: await loadDeliveryConfig(root),
389
+ });
390
+ writeFileSync(join(root, '.git', 'ai-delivery', 'runtime-admission.json'), JSON.stringify(selectedAdmission), {
391
+ mode: 0o600,
392
+ });
393
+ const row = await prepareIssueWorktree({
394
+ identity: options.custodianIdentity ?? identity,
395
+ issueNumber: 17,
396
+ repoRoot: root,
397
+ });
398
+ const outputRoot = join(root, '.git', 'source-phase-output');
399
+ const callerPath = join(root, '.git', 'source-phase-caller.mjs');
400
+ const authorizationPath = join(root, '.git', 'source-phase-authorization.json');
401
+ writeFileSync(callerPath, '// frozen synthetic source caller\n', { mode: 0o600 });
402
+ writeFileSync(authorizationPath, '{"source":"synthetic reviewed source allocation"}\n', { mode: 0o600 });
403
+ const executable = realpathSync(process.execPath);
404
+ const stat = lstatSync(executable);
405
+ const commands = [0, 1, 2].map((index) => ({
406
+ argv: [executable, '-e', `console.log(process.env.SOURCE_PHASE_VALUE + ':${String(index)}')`],
407
+ cwd: row.path,
408
+ executable: {
409
+ path: executable,
410
+ digest: sha256(executable),
411
+ device: stat.dev,
412
+ inode: stat.ino,
413
+ uid: stat.uid,
414
+ mode: stat.mode,
415
+ },
416
+ }));
417
+ const overrides = { SOURCE_PHASE_VALUE: 'frozen' };
418
+ const environment = { ...process.env, ...overrides };
419
+ delete environment.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
420
+ const admission = await assertDeliveryRuntimeAdmitted({ repoRoot: root, runtimeEntryPath });
421
+ const input = {
422
+ repoRoot: root,
423
+ issueNumber: 17,
424
+ identity,
425
+ rowDigest: digestValue(row),
426
+ controller: {
427
+ head: git(root, 'rev-parse', 'HEAD'),
428
+ configDigest: (await loadDeliveryConfig(root)).configDigest,
429
+ admissionId: admission.admissionId,
430
+ runtimeEntryPath,
431
+ packageVersion: admission.packageVersion,
432
+ archiveDigest: admission.sourceArchiveSha256,
433
+ },
434
+ source: {
435
+ path: row.path,
436
+ branch: row.branch,
437
+ head: git(row.path, 'rev-parse', 'HEAD'),
438
+ indexTree: git(row.path, 'write-tree'),
439
+ configDigest: (await loadDeliveryConfig(row.path)).configDigest,
440
+ dirty: [],
441
+ effect: { kind: 'preserve' },
442
+ },
443
+ caller: { path: callerPath, digest: sha256(callerPath) },
444
+ authorization: { path: authorizationPath, digest: sha256(authorizationPath) },
445
+ inputs: [],
446
+ commands,
447
+ commandGraphDigest: digestValue(commands),
448
+ environment: { digest: digestValue(environment), overrides },
449
+ bounds: {
450
+ maxAggregateRssBytes: 1024 ** 3,
451
+ maxNewOutputBytes: 4 * 1024 ** 2,
452
+ minFreeDiskBytes: 1,
453
+ outputRoots: [outputRoot, join(root, '.git', 'ai-delivery')],
454
+ },
455
+ completionArtifacts: [join(outputRoot, 'metadata.json')],
456
+ };
457
+ return { ...fixtureResult, runtimeEntryPath, row, outputRoot, input, selectedAdmission };
458
+ }
459
+ function sourcePhaseRecords(root) {
460
+ const directory = join(gitCommonDir(root), 'ai-delivery', 'receipts', 'source-phase@1');
461
+ return readdirSync(directory).flatMap((subject) => readdirSync(join(directory, subject)).map((name) => JSON.parse(readFileSync(join(directory, subject, name), 'utf8'))));
462
+ }
463
+ test('supported issue source phase freezes three ordered commands and reuses only the completed whole phase', async () => {
464
+ const { root, outputRoot, input, selectedAdmission } = await sourcePhaseFixture();
465
+ try {
466
+ const agent = await import('./agent.js');
467
+ assert.equal(typeof agent.withIssueSourcePhase, 'function', 'the source capability must be public');
468
+ assert.notEqual(selectedAdmission.sourceCommit, input.controller.head, 'producer and consumer are separate repositories');
469
+ let entered = 0;
470
+ const receipt = await agent.withIssueSourcePhase(input, async (context) => {
471
+ entered += 1;
472
+ mkdirSync(outputRoot, { mode: 0o700 });
473
+ writeFileSync(join(outputRoot, 'metadata.json'), '{"owned":true}\n');
474
+ const inherited = process.env.SOURCE_PHASE_VALUE;
475
+ process.env.SOURCE_PHASE_VALUE = 'changed-after-freeze';
476
+ try {
477
+ for (let index = 0; index < input.commands.length; index++) {
478
+ assert.equal((await context.run(index)).toString().trim(), `frozen:${String(index)}`);
479
+ }
480
+ }
481
+ finally {
482
+ if (inherited === undefined)
483
+ delete process.env.SOURCE_PHASE_VALUE;
484
+ else
485
+ process.env.SOURCE_PHASE_VALUE = inherited;
486
+ }
487
+ });
488
+ assert.equal(receipt.status, 'complete');
489
+ assert.equal(receipt.source.head, input.source.head);
490
+ assert.equal(receipt.commands.length, 3);
491
+ assert.equal(sourcePhaseRecords(root)[0]?.status, 'complete');
492
+ const reused = await agent.withIssueSourcePhase(input, async () => {
493
+ entered += 1;
494
+ });
495
+ assert.equal(reused.recordId, receipt.recordId);
496
+ assert.equal(entered, 1);
497
+ assert.equal(readdirSync(join(gitCommonDir(root), 'ai-delivery', 'writers@1')).some((name) => name.endsWith('.json')), false);
498
+ }
499
+ finally {
500
+ rmSync(root, { recursive: true, force: true });
501
+ }
502
+ });
503
+ test('supported issue source phase preserves historical custody under the configured authenticated author', async () => {
504
+ const { root, row, outputRoot, input } = await sourcePhaseFixture({
505
+ personalAuthor: true,
506
+ custodianIdentity: 'historical-builder',
507
+ });
508
+ const owners = join(gitCommonDir(root), 'ai-delivery', 'worktree-owners');
509
+ const witnessNames = readdirSync(owners);
510
+ const witnesses = witnessNames.map((name) => ({ path: join(owners, name), bytes: readFileSync(join(owners, name)) }));
511
+ const registryPath = join(root, '.issue-cli', 'worktrees.json');
512
+ const registryBytes = readFileSync(registryPath);
513
+ const registered = getIssueWorktreeStrict(input.issueNumber, root);
514
+ let entered = 0;
515
+ try {
516
+ const { withIssueSourcePhase } = await import('./agent.js');
517
+ assert.equal(row.identity, 'historical-builder');
518
+ assert.equal(input.identity, 'host-author');
519
+ const receipt = await withIssueSourcePhase(input, async (context) => {
520
+ entered += 1;
521
+ assert.equal(context.actor.identity, input.identity);
522
+ mkdirSync(outputRoot);
523
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
524
+ for (let index = 0; index < input.commands.length; index++)
525
+ await context.run(index);
526
+ });
527
+ assert.equal(receipt.status, 'complete');
528
+ assert.equal(receipt.commands.length, input.commands.length);
529
+ const record = sourcePhaseRecords(root).find((value) => value.recordId === receipt.recordId);
530
+ assert.equal(record.actor.identity, 'host-author');
531
+ assert.equal(record.binding.rowDigest, digestValue(registered));
532
+ assert.deepEqual(getIssueWorktreeStrict(input.issueNumber, root), registered);
533
+ assert.ok(readFileSync(registryPath).equals(registryBytes));
534
+ assert.deepEqual(readdirSync(owners), witnessNames);
535
+ for (const witness of witnesses)
536
+ assert.ok(readFileSync(witness.path).equals(witness.bytes));
537
+ const reused = await withIssueSourcePhase(input, async () => {
538
+ entered += 1;
539
+ });
540
+ assert.equal(reused.recordId, receipt.recordId);
541
+ assert.equal(entered, 1);
542
+ assert.deepEqual(getIssueWorktreeStrict(input.issueNumber, root), registered);
543
+ assert.ok(readFileSync(registryPath).equals(registryBytes));
544
+ for (const witness of witnesses)
545
+ assert.ok(readFileSync(witness.path).equals(witness.bytes));
546
+ }
547
+ finally {
548
+ rmSync(root, { recursive: true, force: true });
549
+ }
550
+ });
551
+ test.each(['author', 'issue', 'worktree', 'row', 'missing-witness', 'changed-witness'])('supported issue source phase preserves historical custody fences for wrong %s', async (mode) => {
552
+ const { root, row, outputRoot, input } = await sourcePhaseFixture({
553
+ personalAuthor: true,
554
+ custodianIdentity: 'historical-builder',
555
+ });
556
+ let entered = false;
557
+ try {
558
+ const { withIssueSourcePhase } = await import('./agent.js');
559
+ if (mode === 'author')
560
+ input.identity = 'historical-builder';
561
+ if (mode === 'issue')
562
+ input.issueNumber += 1;
563
+ if (mode === 'worktree')
564
+ input.source.path = root;
565
+ if (mode === 'row')
566
+ input.rowDigest = digestValue({ ...row, identity: input.identity });
567
+ if (mode === 'missing-witness' || mode === 'changed-witness') {
568
+ const owners = join(gitCommonDir(root), 'ai-delivery', 'worktree-owners');
569
+ const names = readdirSync(owners);
570
+ assert.equal(names.length, 1);
571
+ const witness = join(owners, names[0]);
572
+ if (mode === 'missing-witness')
573
+ unlinkSync(witness);
574
+ else {
575
+ const stored = JSON.parse(readFileSync(witness, 'utf8'));
576
+ stored.ownerId = digestValue('tampered witness');
577
+ writeFileSync(witness, JSON.stringify(stored));
578
+ }
579
+ }
580
+ await assert.rejects(withIssueSourcePhase(input, async () => {
581
+ entered = true;
582
+ }), mode === 'author'
583
+ ? /actor.*binding changed/u
584
+ : mode === 'issue'
585
+ ? /canonical registry row/u
586
+ : mode === 'worktree' || mode === 'row'
587
+ ? /exact registered issue owner/u
588
+ : /ownership witness/u);
589
+ assert.equal(entered, false);
590
+ assert.equal(existsSync(outputRoot), false, 'no source command or callback output was created');
591
+ }
592
+ finally {
593
+ rmSync(root, { recursive: true, force: true });
594
+ }
595
+ });
596
+ test('supported issue source phase refuses a linked worktree as its primary controller', async () => {
597
+ const { root, row, input } = await sourcePhaseFixture();
598
+ input.repoRoot = row.path;
599
+ let entered = false;
600
+ try {
601
+ const { withIssueSourcePhase } = await import('./agent.js');
602
+ await assert.rejects(withIssueSourcePhase(input, async () => {
603
+ entered = true;
604
+ }), /canonical primary controller/u);
605
+ assert.equal(entered, false);
606
+ }
607
+ finally {
608
+ rmSync(root, { recursive: true, force: true });
609
+ }
610
+ });
611
+ test('supported issue source phase authenticates its configured author before callback entry', async () => {
612
+ const { root, input } = await sourcePhaseFixture();
613
+ let entered = false;
614
+ const unavailable = vi
615
+ .spyOn(githubClient, 'createDeliveryGitHubClients')
616
+ .mockRejectedValueOnce(new Error('Missing selected author credentials'));
617
+ try {
618
+ const { withIssueSourcePhase } = await import('./agent.js');
619
+ await assert.rejects(withIssueSourcePhase(input, async () => {
620
+ entered = true;
621
+ }), /Missing selected author credentials/u);
622
+ assert.equal(entered, false);
623
+ }
624
+ finally {
625
+ unavailable.mockRestore();
626
+ rmSync(root, { recursive: true, force: true });
627
+ }
628
+ });
629
+ test('supported issue source phase refuses an admitted CLI from a different executing installation', async () => {
630
+ const { root, input, selectedAdmission } = await sourcePhaseFixture();
631
+ const selectedRoot = join(root, '.git', 'different-selected-runtime');
632
+ mkdirSync(selectedRoot);
633
+ const sdkRoot = dirname(dirname(input.controller.runtimeEntryPath));
634
+ cpSync(join(sdkRoot, 'dist'), join(selectedRoot, 'dist'), { recursive: true });
635
+ cpSync(join(sdkRoot, 'package.json'), join(selectedRoot, 'package.json'));
636
+ const admission = buildRuntimeAdmission({
637
+ ...selectedAdmission,
638
+ cliPath: join(selectedRoot, 'dist', 'cli.js'),
639
+ configuration: await loadDeliveryConfig(root),
640
+ });
641
+ writeFileSync(join(root, '.git', 'ai-delivery', 'runtime-admission.json'), JSON.stringify(admission), {
642
+ mode: 0o600,
643
+ });
644
+ input.controller.runtimeEntryPath = admission.cliPath;
645
+ input.controller.admissionId = admission.admissionId;
646
+ let entered = false;
647
+ try {
648
+ const { withIssueSourcePhase } = await import('./agent.js');
649
+ await assert.rejects(withIssueSourcePhase(input, async () => {
650
+ entered = true;
651
+ }), /executing.*installation/u);
652
+ assert.equal(entered, false);
653
+ }
654
+ finally {
655
+ rmSync(root, { recursive: true, force: true });
656
+ }
657
+ });
658
+ test('supported issue source phase refuses a matching existing commit without completed phase proof', async () => {
659
+ const { root, row, outputRoot, input } = await sourcePhaseFixture();
660
+ input.source.effect = { kind: 'commitOnce', parent: input.source.head, tree: input.source.indexTree };
661
+ const committed = git(row.path, 'commit-tree', input.source.indexTree, '-p', input.source.head, '-m', 'Previously published source');
662
+ git(row.path, 'update-ref', `refs/heads/${row.branch}`, committed);
663
+ let entered = false;
664
+ try {
665
+ const { withIssueSourcePhase } = await import('./agent.js');
666
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
667
+ entered = true;
668
+ mkdirSync(outputRoot);
669
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
670
+ for (let index = 0; index < input.commands.length; index++)
671
+ await context.run(index);
672
+ }), /initial source/u);
673
+ assert.equal(entered, false);
674
+ }
675
+ finally {
676
+ rmSync(root, { recursive: true, force: true });
677
+ }
678
+ });
679
+ test('supported issue source phase cannot replay a failed command even when its callback catches the error', async () => {
680
+ const { root, outputRoot, input } = await sourcePhaseFixture();
681
+ const counter = join(outputRoot, 'attempts.txt');
682
+ input.commands[0].argv = [
683
+ realpathSync(process.execPath),
684
+ '-e',
685
+ `const fs=require('fs');fs.appendFileSync(${JSON.stringify(counter)},'x');if(fs.readFileSync(${JSON.stringify(counter)},'utf8').length===1)process.exit(7);`,
686
+ ];
687
+ input.commandGraphDigest = digestValue(input.commands);
688
+ try {
689
+ const { withIssueSourcePhase } = await import('./agent.js');
690
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
691
+ mkdirSync(outputRoot, { mode: 0o700 });
692
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
693
+ await context.run(0).catch(() => undefined);
694
+ await context.run(0);
695
+ await context.run(1);
696
+ await context.run(2);
697
+ }), /ordered|single-use|failed|command/u);
698
+ assert.equal(readFileSync(counter, 'utf8'), 'x');
699
+ }
700
+ finally {
701
+ rmSync(root, { recursive: true, force: true });
702
+ }
703
+ });
704
+ test('supported issue source phase retains complete rejected bootstrap metadata through corrected attempts', async () => {
705
+ const { root, outputRoot, input } = await sourcePhaseFixture();
706
+ const originalGraph = input.commandGraphDigest;
707
+ try {
708
+ const { withIssueSourcePhase } = await import('./agent.js');
709
+ for (let attempt = 0; attempt < 3; attempt++) {
710
+ input.commandGraphDigest = digestValue(`invalid graph ${String(attempt)}`);
711
+ await assert.rejects(withIssueSourcePhase(input, async () => {
712
+ assert.fail('rejected inputs must not enter callback');
713
+ }), /binding changed/u);
714
+ const records = sourcePhaseRecords(root);
715
+ const predecessor = records.find((record) => !records.some((later) => later.reconciliation?.phaseId === record.phaseId));
716
+ assert.equal(predecessor.status, 'rejected-before-work');
717
+ assert.ok(Number(predecessor.bootstrapBytes) >= Buffer.byteLength(`${JSON.stringify(predecessor, null, 2)}\n`), 'charge the complete retained rejection');
718
+ assert.ok(predecessor.binding, 'preserve rejected requested allocation and input bindings');
719
+ input.reconciliation = {
720
+ phaseId: String(predecessor.phaseId),
721
+ recordId: String(predecessor.recordId),
722
+ authorizationDigest: input.authorization.digest,
723
+ };
724
+ }
725
+ const rejected = sourcePhaseRecords(root);
726
+ const retainedBootstrap = rejected.reduce((sum, record) => sum + Number(record.bootstrapBytes), 0);
727
+ input.commandGraphDigest = originalGraph;
728
+ const completed = await withIssueSourcePhase(input, async (context) => {
729
+ mkdirSync(outputRoot);
730
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
731
+ for (let index = 0; index < input.commands.length; index++)
732
+ await context.run(index);
733
+ });
734
+ const result = sourcePhaseRecords(root).find((record) => record.recordId === completed.recordId);
735
+ assert.ok(Number(result.result.newOutputBytes) >= retainedBootstrap);
736
+ assert.equal(sourcePhaseRecords(root).length, 4, 'retain every rejection');
737
+ }
738
+ finally {
739
+ rmSync(root, { recursive: true, force: true });
740
+ }
741
+ });
742
+ test('supported issue source phase marks changed frozen inputs unresolved after callback failure', async () => {
743
+ const { root, input } = await sourcePhaseFixture();
744
+ const frozen = join(root, '.git', 'frozen-input.json');
745
+ writeFileSync(frozen, '{}');
746
+ input.inputs = [{ path: frozen, digest: sha256(frozen) }];
747
+ try {
748
+ const { withIssueSourcePhase } = await import('./agent.js');
749
+ await assert.rejects(withIssueSourcePhase(input, async () => {
750
+ writeFileSync(frozen, '{"changed":true}');
751
+ throw new Error('callback failed');
752
+ }), /unresolved/u);
753
+ assert.equal(sourcePhaseRecords(root)[0]?.status, 'unresolved');
754
+ }
755
+ finally {
756
+ rmSync(root, { recursive: true, force: true });
757
+ }
758
+ });
759
+ test('supported issue source phase reconciliation cannot forget original frozen inputs', async () => {
760
+ const { root, input } = await sourcePhaseFixture();
761
+ const frozen = join(root, '.git', 'frozen-input.json');
762
+ writeFileSync(frozen, '{}');
763
+ input.inputs = [{ path: frozen, digest: sha256(frozen) }];
764
+ try {
765
+ const { withIssueSourcePhase } = await import('./agent.js');
766
+ await assert.rejects(withIssueSourcePhase(input, async () => {
767
+ throw new Error('quiescent failure');
768
+ }), /failed-quiescent/u);
769
+ const predecessor = sourcePhaseRecords(root)[0];
770
+ const baseline = predecessor.baseline;
771
+ input.reconciliation = {
772
+ phaseId: String(predecessor.phaseId),
773
+ recordId: String(predecessor.recordId),
774
+ baselineId: baseline.baselineId,
775
+ authorizationDigest: input.authorization.digest,
776
+ };
777
+ input.inputs = [];
778
+ let entered = false;
779
+ await assert.rejects(withIssueSourcePhase(input, async () => {
780
+ entered = true;
781
+ }), /compatible|retained.*input/u);
782
+ assert.equal(entered, false);
783
+ }
784
+ finally {
785
+ rmSync(root, { recursive: true, force: true });
786
+ }
787
+ });
788
+ test('supported issue source phase stops streaming capture at its remaining cumulative allowance', async () => {
789
+ const { root, outputRoot, input } = await sourcePhaseFixture();
790
+ input.bounds.maxNewOutputBytes = 128 * 1024;
791
+ input.commands[0].argv = [
792
+ realpathSync(process.execPath),
793
+ '-e',
794
+ 'process.stdout.write(Buffer.alloc(256*1024,120));setInterval(()=>{},1000)',
795
+ ];
796
+ input.commandGraphDigest = digestValue(input.commands);
797
+ input.completionArtifacts = [];
798
+ const cancellation = new AbortController();
799
+ input.signal = cancellation.signal;
800
+ const diagnosticDeadline = setTimeout(() => cancellation.abort(), 3_000);
801
+ try {
802
+ const { withIssueSourcePhase } = await import('./agent.js');
803
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
804
+ mkdirSync(outputRoot);
805
+ await context.run(0);
806
+ }), /captured.*allowance/u);
807
+ assert.equal(cancellation.signal.aborted, false, 'streaming bound stops the waiting command before diagnostic cancellation');
808
+ assert.equal(readdirSync(join(gitCommonDir(root), 'ai-delivery', 'writers@1')).some((name) => name.endsWith('.json')), false);
809
+ }
810
+ finally {
811
+ clearTimeout(diagnosticDeadline);
812
+ rmSync(root, { recursive: true, force: true });
813
+ }
814
+ });
815
+ test('supported issue source phase controls stale-lock compromise while a real competing writer is excluded', async () => {
816
+ const { root, row, outputRoot, input } = await sourcePhaseFixture({
817
+ personalAuthor: true,
818
+ custodianIdentity: 'historical-builder',
819
+ });
820
+ const paused = join(root, '.git', 'source-phase-validation-paused');
821
+ const originalLoad = deliveryConfiguration.loadDeliveryConfig;
822
+ let pauseOnce = true;
823
+ const slowValidation = vi.spyOn(deliveryConfiguration, 'loadDeliveryConfig').mockImplementation(async (...args) => {
824
+ if (pauseOnce && args[0] === root) {
825
+ pauseOnce = false;
826
+ writeFileSync(paused, 'writer fence is live');
827
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 12_500);
828
+ }
829
+ return originalLoad(...args);
830
+ });
831
+ const contender = spawn(process.execPath, [
832
+ '-e',
833
+ `(async()=>{
834
+ const fs=await import('node:fs');
835
+ while(!fs.existsSync(${JSON.stringify(paused)}))await new Promise(r=>setTimeout(r,20));
836
+ await new Promise(r=>setTimeout(r,11_000));
837
+ const {withRuntimeSetupWriter}=await import(${JSON.stringify(new URL('./verification.js', import.meta.url).href)});
838
+ await withRuntimeSetupWriter(${JSON.stringify(row.path)},async()=>{console.log('COMPETING_CALLBACK_ENTERED')});
839
+ })().catch(e=>{console.log(e.message);process.exitCode=1});`,
840
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
841
+ let contenderOutput = '';
842
+ let entered = false;
843
+ contender.stdout.on('data', (bytes) => {
844
+ contenderOutput += bytes.toString();
845
+ });
846
+ contender.stderr.on('data', (bytes) => {
847
+ contenderOutput += bytes.toString();
848
+ });
849
+ const closed = new Promise((resolve) => contender.once('close', () => resolve()));
850
+ try {
851
+ const { withIssueSourcePhase } = await import('./agent.js');
852
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
853
+ entered = true;
854
+ mkdirSync(outputRoot);
855
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
856
+ for (let index = 0; index < input.commands.length; index++)
857
+ await context.run(index);
858
+ }), /unresolved/u);
859
+ await closed;
860
+ assert.match(contenderOutput, /unsealed source phase/u);
861
+ assert.equal(entered, false, 'compromise cancels before callback entry');
862
+ assert.doesNotMatch(contenderOutput, /COMPETING_CALLBACK_ENTERED/u);
863
+ const record = sourcePhaseRecords(root)[0];
864
+ assert.equal(record.status, 'unresolved');
865
+ assert.ok(record.failure.lockRelease);
866
+ const writers = join(gitCommonDir(root), 'ai-delivery', 'writers@1');
867
+ assert.equal(readdirSync(writers).length, 0, 'writer and reclaimed lock are absent after controlled failure');
868
+ }
869
+ finally {
870
+ slowValidation.mockRestore();
871
+ if (contender.exitCode === null && contender.signalCode === null)
872
+ contender.kill('SIGKILL');
873
+ await closed;
874
+ rmSync(root, { recursive: true, force: true });
875
+ }
876
+ }, 30_000);
877
+ test('supported issue source phase runs fixture tests, an enabled-hook commit and classification with separate controller source', async () => {
878
+ const { root, row, outputRoot, input } = await sourcePhaseFixture({
879
+ personalAuthor: true,
880
+ custodianIdentity: 'historical-builder',
881
+ });
882
+ const hook = join(root, '.git', 'source-phase-hooks', 'pre-commit');
883
+ mkdirSync(dirname(hook));
884
+ writeFileSync(hook, `#!/bin/sh\nprintf 'enabled\\n' >> ${JSON.stringify(join(outputRoot, 'hook-count.txt'))}\n`, {
885
+ mode: 0o700,
886
+ });
887
+ writeFileSync(join(row.path, 'artifact.txt'), 'frozen staged source change\n');
888
+ git(row.path, 'add', 'artifact.txt');
889
+ input.source.indexTree = git(row.path, 'write-tree');
890
+ input.source.dirty = [{ path: 'artifact.txt', digest: sha256(join(row.path, 'artifact.txt')) }];
891
+ input.source.effect = { kind: 'commitOnce', parent: input.source.head, tree: input.source.indexTree };
892
+ input.inputs = [{ path: hook, digest: sha256(hook) }];
893
+ const gitPath = realpathSync(execFileSync('/usr/bin/which', ['git'], { encoding: 'utf8' }).trim());
894
+ const stat = lstatSync(gitPath);
895
+ input.commands[0].argv = [
896
+ realpathSync(process.execPath),
897
+ '-e',
898
+ "for(let i=0;i<18;i++)console.log('fixture '+i+' passed')",
899
+ ];
900
+ input.commands[1] = {
901
+ cwd: row.path,
902
+ argv: [gitPath, '-c', `core.hooksPath=${dirname(hook)}`, 'commit', '-m', 'Reviewed source phase'],
903
+ executable: {
904
+ path: gitPath,
905
+ digest: sha256(gitPath),
906
+ device: stat.dev,
907
+ inode: stat.ino,
908
+ uid: stat.uid,
909
+ mode: stat.mode,
910
+ },
911
+ };
912
+ input.commands[2].argv = [
913
+ realpathSync(process.execPath),
914
+ '-e',
915
+ "const c=require('child_process');if(c.execFileSync('git',['status','--porcelain'],{encoding:'utf8'}).trim())process.exit(8);console.log('source classification passed')",
916
+ ];
917
+ input.commandGraphDigest = digestValue(input.commands);
918
+ try {
919
+ const { withIssueSourcePhase } = await import('./agent.js');
920
+ let entered = 0;
921
+ const receipt = await withIssueSourcePhase(input, async (context) => {
922
+ entered += 1;
923
+ mkdirSync(outputRoot);
924
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
925
+ assert.equal((await context.run(0)).toString().trim().split('\n').length, 18);
926
+ await context.run(1);
927
+ await context.run(2);
928
+ });
929
+ assert.notEqual(receipt.source.head, input.source.head);
930
+ assert.equal(git(root, 'rev-parse', 'HEAD'), input.controller.head);
931
+ assert.equal(git(row.path, 'rev-parse', 'HEAD^'), input.source.head);
932
+ assert.equal(receipt.source.indexTree, input.source.indexTree);
933
+ assert.equal(readFileSync(join(outputRoot, 'hook-count.txt'), 'utf8'), 'enabled\n');
934
+ const reused = await withIssueSourcePhase(input, async () => {
935
+ entered += 1;
936
+ });
937
+ assert.equal(reused.recordId, receipt.recordId);
938
+ assert.equal(entered, 1);
939
+ }
940
+ finally {
941
+ rmSync(root, { recursive: true, force: true });
942
+ }
943
+ });
944
+ test('supported issue source phase never replays a commit followed by callback failure', async () => {
945
+ const { root, row, input } = await sourcePhaseFixture();
946
+ input.source.effect = { kind: 'commitOnce', parent: input.source.head, tree: input.source.indexTree };
947
+ input.completionArtifacts = [];
948
+ try {
949
+ const { withIssueSourcePhase } = await import('./agent.js');
950
+ await assert.rejects(withIssueSourcePhase(input, async () => {
951
+ const committed = git(row.path, 'commit-tree', input.source.indexTree, '-p', input.source.head, '-m', 'Uncertain commit');
952
+ git(row.path, 'update-ref', `refs/heads/${row.branch}`, committed);
953
+ throw new Error('failure after commit');
954
+ }), /unresolved/u);
955
+ const old = sourcePhaseRecords(root)[0];
956
+ assert.equal(old.status, 'unresolved');
957
+ let entered = false;
958
+ await assert.rejects(withIssueSourcePhase(input, async () => {
959
+ entered = true;
960
+ }), /incomplete|Unresolved/u);
961
+ assert.equal(entered, false);
962
+ assert.deepEqual(sourcePhaseRecords(root)[0], old, 'uncertain evidence is immutable');
963
+ }
964
+ finally {
965
+ rmSync(root, { recursive: true, force: true });
966
+ }
967
+ });
968
+ test('supported issue source phase rejects skipped and concurrent commands and expires escaped capabilities', async () => {
969
+ for (const mode of ['skipped', 'concurrent', 'escaped']) {
970
+ const { root, outputRoot, input } = await sourcePhaseFixture();
971
+ let escaped;
972
+ try {
973
+ const { withIssueSourcePhase } = await import('./agent.js');
974
+ const operation = withIssueSourcePhase(input, async (context) => {
975
+ escaped = context.run;
976
+ mkdirSync(outputRoot);
977
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
978
+ if (mode === 'skipped')
979
+ await context.run(1);
980
+ else if (mode === 'concurrent')
981
+ await Promise.all([context.run(0), context.run(1)]);
982
+ else
983
+ for (let index = 0; index < input.commands.length; index++)
984
+ await context.run(index);
985
+ });
986
+ if (mode === 'escaped')
987
+ await operation;
988
+ else
989
+ await assert.rejects(operation, /ordered|single-use|cancelled|unawaited/u);
990
+ await assert.rejects(escaped(0), /expired/u);
991
+ assert.equal(readdirSync(join(gitCommonDir(root), 'ai-delivery', 'writers@1')).some((name) => name.endsWith('.json')), false);
992
+ }
993
+ finally {
994
+ rmSync(root, { recursive: true, force: true });
995
+ }
996
+ }
997
+ });
998
+ test('supported issue source phase cancels unawaited commands and preserves unrelated output', async () => {
999
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1000
+ const unrelated = join(root, '.git', 'unrelated-preserved.txt');
1001
+ writeFileSync(unrelated, 'another run');
1002
+ input.commands[0].argv = [realpathSync(process.execPath), '-e', 'setInterval(()=>{},1000)'];
1003
+ input.commandGraphDigest = digestValue(input.commands);
1004
+ try {
1005
+ const { withIssueSourcePhase } = await import('./agent.js');
1006
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
1007
+ mkdirSync(outputRoot);
1008
+ void context.run(0).catch(() => undefined);
1009
+ }), /unawaited/u);
1010
+ assert.equal(readFileSync(unrelated, 'utf8'), 'another run');
1011
+ const record = sourcePhaseRecords(root)[0];
1012
+ assert.equal(record.status, 'failed-quiescent');
1013
+ assert.equal(record.failure.cleanup, undefined);
1014
+ assert.equal(readdirSync(join(gitCommonDir(root), 'ai-delivery', 'writers@1')).length, 0);
1015
+ }
1016
+ finally {
1017
+ rmSync(root, { recursive: true, force: true });
1018
+ }
1019
+ });
1020
+ test('supported issue source phase refuses interrupted intent without replay or changing its retained checkpoint', async () => {
1021
+ const { root, outputRoot, input } = await sourcePhaseFixture({ personalAuthor: true });
1022
+ input.environment.overrides.AUTHOR_TOKEN = 'synthetic-selected-author';
1023
+ const environment = { ...process.env };
1024
+ delete environment.AI_DELIVERY_OUTPUT_OBSERVATION_GATE;
1025
+ for (const [name, value] of Object.entries(input.environment.overrides)) {
1026
+ if (value === null)
1027
+ delete environment[name];
1028
+ else
1029
+ environment[name] = value;
1030
+ }
1031
+ input.environment.digest = digestValue(environment);
1032
+ const completedUnit = join(outputRoot, 'completed-unit.json');
1033
+ const child = spawn(process.execPath, [
1034
+ '-e',
1035
+ `(async()=>{
1036
+ const {syntheticDiscoveryClients}=await import(${JSON.stringify(new URL('./fixtures/discovery.js', import.meta.url).href)});
1037
+ globalThis.fetch=async(url,request)=>{
1038
+ const address=String(url);
1039
+ if(address.endsWith('/graphql')){
1040
+ const body=JSON.parse(request.body);const data=await syntheticDiscoveryClients().graphql(body.query,body.variables);
1041
+ return new Response(JSON.stringify({data}),{status:200,headers:{'content-type':'application/json'}});
1042
+ }
1043
+ if(address.endsWith('/user'))return new Response(JSON.stringify({login:'host-user',id:37}),{status:200,headers:{'content-type':'application/json'}});
1044
+ throw new Error('Unexpected synthetic native request: '+address);
1045
+ };
1046
+ const {withIssueSourcePhase}=await import(${JSON.stringify(new URL('./agent.js', import.meta.url).href)});
1047
+ const fs=await import('node:fs');
1048
+ await withIssueSourcePhase(${JSON.stringify(input)},async context=>{
1049
+ fs.mkdirSync(${JSON.stringify(outputRoot)});
1050
+ await context.run(0);
1051
+ fs.writeFileSync(${JSON.stringify(completedUnit)},JSON.stringify({unit:0,output:'frozen:0'}));
1052
+ await new Promise(()=>setInterval(()=>{},1000));
1053
+ });
1054
+ })().catch(e=>{console.error(e.message);process.exitCode=1});`,
1055
+ ], { env: environment, stdio: ['ignore', 'pipe', 'pipe'] });
1056
+ let output = '';
1057
+ const capture = (bytes) => {
1058
+ output += bytes.toString();
1059
+ if (output.length > 64 * 1024)
1060
+ child.kill('SIGKILL');
1061
+ };
1062
+ child.stdout.on('data', capture);
1063
+ child.stderr.on('data', capture);
1064
+ const closed = new Promise((resolve) => child.once('close', () => resolve()));
1065
+ try {
1066
+ for (let attempt = 0; attempt < 500 && !existsSync(completedUnit) && child.exitCode === null; attempt++)
1067
+ await new Promise((resolve) => setTimeout(resolve, 20));
1068
+ assert.ok(existsSync(completedUnit), output);
1069
+ child.kill('SIGKILL');
1070
+ await closed;
1071
+ assert.equal(child.signalCode, 'SIGKILL');
1072
+ const record = sourcePhaseRecords(root)[0];
1073
+ assert.equal(record.status, 'intent');
1074
+ assert.equal(record.commands.length, 1);
1075
+ const writer = join(gitCommonDir(root), 'ai-delivery', 'writers@1', `${digestValue(input.source.path).slice(7)}.json`);
1076
+ const beforeWriter = readFileSync(writer);
1077
+ const stale = new Date(Date.now() - 20_000);
1078
+ utimesSync(`${writer}.lock`, stale, stale);
1079
+ const { withIssueSourcePhase } = await import('./agent.js');
1080
+ let entered = false;
1081
+ await assert.rejects(withIssueSourcePhase(input, async () => {
1082
+ entered = true;
1083
+ }), /unsealed source phase/u);
1084
+ assert.equal(entered, false);
1085
+ assert.deepEqual(sourcePhaseRecords(root)[0], record);
1086
+ assert.ok(readFileSync(writer).equals(beforeWriter));
1087
+ assert.equal(readFileSync(completedUnit, 'utf8'), '{"unit":0,"output":"frozen:0"}');
1088
+ }
1089
+ finally {
1090
+ if (child.exitCode === null && child.signalCode === null)
1091
+ child.kill('SIGKILL');
1092
+ await closed;
1093
+ rmSync(root, { recursive: true, force: true });
1094
+ }
1095
+ }, 20_000);
1096
+ test('supported issue source phase charges output before, between and after its frozen commands', async () => {
1097
+ for (const timing of ['before', 'between', 'after']) {
1098
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1099
+ const counter = join(outputRoot, 'started.txt');
1100
+ input.bounds.maxNewOutputBytes = 64 * 1024;
1101
+ for (let index = 0; index < input.commands.length; index++)
1102
+ input.commands[index].argv = [
1103
+ realpathSync(process.execPath),
1104
+ '-e',
1105
+ `require('fs').appendFileSync(${JSON.stringify(counter)},${JSON.stringify(String(index))})`,
1106
+ ];
1107
+ input.commandGraphDigest = digestValue(input.commands);
1108
+ input.completionArtifacts = [];
1109
+ try {
1110
+ const { withIssueSourcePhase } = await import('./agent.js');
1111
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
1112
+ mkdirSync(outputRoot);
1113
+ if (timing === 'before')
1114
+ writeFileSync(join(outputRoot, 'before.bin'), Buffer.alloc(80 * 1024));
1115
+ if (timing === 'between')
1116
+ writeFileSync(join(outputRoot, 'first.bin'), Buffer.alloc(30 * 1024));
1117
+ await context.run(0);
1118
+ if (timing === 'between')
1119
+ writeFileSync(join(outputRoot, 'second.bin'), Buffer.alloc(40 * 1024));
1120
+ await context.run(1);
1121
+ await context.run(2);
1122
+ if (timing === 'after')
1123
+ writeFileSync(join(outputRoot, 'after.bin'), Buffer.alloc(80 * 1024));
1124
+ }), /output|allowance/u);
1125
+ assert.equal(existsSync(counter) ? readFileSync(counter, 'utf8') : '', timing === 'before' ? '' : timing === 'between' ? '0' : '012');
1126
+ assert.notEqual(sourcePhaseRecords(root)[0]?.status, 'complete');
1127
+ }
1128
+ finally {
1129
+ rmSync(root, { recursive: true, force: true });
1130
+ }
1131
+ }
1132
+ });
1133
+ test('supported issue source phase resumes a quiescent failure with its original baseline and ends the predecessor chain', async () => {
1134
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1135
+ input.bounds.maxNewOutputBytes = 256 * 1024;
1136
+ try {
1137
+ const { withIssueSourcePhase } = await import('./agent.js');
1138
+ await assert.rejects(withIssueSourcePhase(input, async () => {
1139
+ mkdirSync(outputRoot);
1140
+ writeFileSync(join(outputRoot, 'retained.bin'), Buffer.alloc(40 * 1024, 1));
1141
+ throw new Error('bounded preparation failure');
1142
+ }), /failed-quiescent/u);
1143
+ const original = sourcePhaseRecords(root)[0];
1144
+ const baseline = original.baseline;
1145
+ input.reconciliation = {
1146
+ phaseId: String(original.phaseId),
1147
+ recordId: String(original.recordId),
1148
+ baselineId: baseline.baselineId,
1149
+ authorizationDigest: input.authorization.digest,
1150
+ };
1151
+ const nextCaller = join(root, '.git', 'next-source-caller.mjs');
1152
+ writeFileSync(nextCaller, '// expressly authorized corrected caller\n');
1153
+ input.caller = { path: nextCaller, digest: sha256(nextCaller) };
1154
+ const receipt = await withIssueSourcePhase(input, async (context) => {
1155
+ writeFileSync(join(outputRoot, 'new.bin'), Buffer.alloc(80 * 1024, 2));
1156
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
1157
+ for (let index = 0; index < input.commands.length; index++)
1158
+ await context.run(index);
1159
+ });
1160
+ const completed = sourcePhaseRecords(root).find((record) => record.recordId === receipt.recordId);
1161
+ assert.equal(completed.baseline.baselineId, baseline.baselineId);
1162
+ assert.ok(completed.result.newOutputBytes >= 120 * 1024);
1163
+ assert.deepEqual(sourcePhaseRecords(root).find((record) => record.recordId === original.recordId), original);
1164
+ const futureCaller = join(root, '.git', 'future-source-caller.mjs');
1165
+ writeFileSync(futureCaller, '// independent later authorized phase\n');
1166
+ input.caller = { path: futureCaller, digest: sha256(futureCaller) };
1167
+ delete input.reconciliation;
1168
+ const future = await withIssueSourcePhase(input, async (context) => {
1169
+ for (let index = 0; index < input.commands.length; index++)
1170
+ await context.run(index);
1171
+ });
1172
+ assert.equal(future.status, 'complete', 'terminal success consumes its failed predecessor chain');
1173
+ }
1174
+ finally {
1175
+ rmSync(root, { recursive: true, force: true });
1176
+ }
1177
+ });
1178
+ test('supported issue source phase refuses changed retained output on an expressly authorized retry', async () => {
1179
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1180
+ const retained = join(outputRoot, 'retained.json');
1181
+ try {
1182
+ const { withIssueSourcePhase } = await import('./agent.js');
1183
+ await assert.rejects(withIssueSourcePhase(input, async () => {
1184
+ mkdirSync(outputRoot);
1185
+ writeFileSync(retained, '{"unit":1}');
1186
+ throw new Error('quiescent failure');
1187
+ }), /failed-quiescent/u);
1188
+ const original = sourcePhaseRecords(root)[0];
1189
+ input.reconciliation = {
1190
+ phaseId: String(original.phaseId),
1191
+ recordId: String(original.recordId),
1192
+ baselineId: original.baseline.baselineId,
1193
+ authorizationDigest: input.authorization.digest,
1194
+ };
1195
+ const graph = input.commandGraphDigest;
1196
+ input.commandGraphDigest = digestValue('invalid intermediate retry graph');
1197
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('invalid intermediate graph cannot enter callback')), /binding changed/u);
1198
+ const intermediate = sourcePhaseRecords(root).find((record) => record.phaseId !== original.phaseId);
1199
+ input.commandGraphDigest = graph;
1200
+ input.reconciliation = {
1201
+ ...input.reconciliation,
1202
+ phaseId: String(intermediate.phaseId),
1203
+ recordId: String(intermediate.recordId),
1204
+ };
1205
+ writeFileSync(retained, '{"unit":2}');
1206
+ let entered = false;
1207
+ await assert.rejects(withIssueSourcePhase(input, async () => {
1208
+ entered = true;
1209
+ }), /retained output artifact identity/u);
1210
+ assert.equal(entered, false);
1211
+ assert.deepEqual(sourcePhaseRecords(root).find((record) => record.recordId === original.recordId), original);
1212
+ }
1213
+ finally {
1214
+ rmSync(root, { recursive: true, force: true });
1215
+ }
1216
+ });
1217
+ test('supported issue source phase cancellation cleans the observed command and descendant identities', async () => {
1218
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1219
+ const pidsPath = join(outputRoot, 'pids.json');
1220
+ input.commands[0].argv = [
1221
+ realpathSync(process.execPath),
1222
+ '-e',
1223
+ `const fs=require('fs'),cp=require('child_process');const child=cp.spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'});fs.writeFileSync(${JSON.stringify(pidsPath)},JSON.stringify({root:process.pid,descendant:child.pid}));setInterval(()=>{},1000);`,
1224
+ ];
1225
+ input.commandGraphDigest = digestValue(input.commands);
1226
+ input.completionArtifacts = [];
1227
+ const cancellation = new AbortController();
1228
+ input.signal = cancellation.signal;
1229
+ const { withIssueSourcePhase } = await import('./agent.js');
1230
+ const operation = withIssueSourcePhase(input, async (context) => {
1231
+ mkdirSync(outputRoot);
1232
+ await context.run(0);
1233
+ }).catch((error) => error);
1234
+ try {
1235
+ for (let attempt = 0; attempt < 500 && !existsSync(pidsPath); attempt++)
1236
+ await new Promise((resolve) => setTimeout(resolve, 20));
1237
+ assert.ok(existsSync(pidsPath));
1238
+ const pids = JSON.parse(readFileSync(pidsPath, 'utf8'));
1239
+ const writer = join(gitCommonDir(root), 'ai-delivery', 'writers@1', `${digestValue(input.source.path).slice(7)}.json`);
1240
+ const observed = () => JSON.parse(readFileSync(writer, 'utf8')).command.tracked?.some((member) => member.pid === pids.descendant) === true;
1241
+ for (let attempt = 0; attempt < 500 && !observed(); attempt++)
1242
+ await new Promise((resolve) => setTimeout(resolve, 20));
1243
+ assert.ok(observed(), 'record actual descendant birth identity before cancellation');
1244
+ cancellation.abort();
1245
+ assert.match(String(await operation), /cancelled/u);
1246
+ for (const pid of [pids.root, pids.descendant]) {
1247
+ const status = spawnSync('ps', ['-p', String(pid), '-o', 'stat='], { encoding: 'utf8' }).stdout.trim();
1248
+ assert.ok(status === '' || status.startsWith('Z'), 'owned identity is no longer running');
1249
+ }
1250
+ const record = sourcePhaseRecords(root)[0];
1251
+ assert.equal(record.failure.cleanup, undefined);
1252
+ assert.equal(readdirSync(join(gitCommonDir(root), 'ai-delivery', 'writers@1')).length, 0);
1253
+ }
1254
+ finally {
1255
+ cancellation.abort();
1256
+ await operation;
1257
+ rmSync(root, { recursive: true, force: true });
1258
+ }
1259
+ }, 20_000);
1260
+ test('supported issue source phase reports writer and lock release failures separately from process cleanup', async () => {
1261
+ for (const released of ['writer', 'lock']) {
1262
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1263
+ try {
1264
+ const { withIssueSourcePhase } = await import('./agent.js');
1265
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
1266
+ mkdirSync(outputRoot);
1267
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
1268
+ for (let index = 0; index < input.commands.length; index++)
1269
+ await context.run(index);
1270
+ const writer = join(gitCommonDir(root), 'ai-delivery', 'writers@1', `${digestValue(input.source.path).slice(7)}.json`);
1271
+ const state = JSON.parse(readFileSync(writer, 'utf8'));
1272
+ assert.equal(state.owner.pid, process.pid, 'fixture only alters its own writer');
1273
+ if (released === 'writer')
1274
+ unlinkSync(writer);
1275
+ else
1276
+ writeFileSync(join(`${writer}.lock`, 'owned-release-obstruction'), 'fixture-owned release failure');
1277
+ }), /unresolved/u);
1278
+ const record = sourcePhaseRecords(root)[0];
1279
+ const failure = record.failure;
1280
+ assert.equal(failure.cleanup, undefined);
1281
+ if (released === 'writer')
1282
+ assert.ok(failure.writerRelease);
1283
+ else {
1284
+ assert.ok(failure.lockRelease);
1285
+ assert.equal(failure.writerRelease, undefined);
1286
+ }
1287
+ assert.equal(record.status, 'unresolved');
1288
+ }
1289
+ finally {
1290
+ rmSync(root, { recursive: true, force: true });
1291
+ }
1292
+ }
1293
+ });
1294
+ test('supported issue source phase retains exclusion through terminal sealing against a real writer', async () => {
1295
+ for (const outcome of ['complete', 'failed-quiescent']) {
1296
+ const { root, row, outputRoot, input } = await sourcePhaseFixture();
1297
+ const writer = join(gitCommonDir(root), 'ai-delivery', 'writers@1', `${digestValue(row.path).slice(7)}.json`);
1298
+ const metadata = join(outputRoot, 'metadata.json');
1299
+ const originalLoad = deliveryConfiguration.loadDeliveryConfig;
1300
+ let contender;
1301
+ const inspectRelease = vi.spyOn(deliveryConfiguration, 'loadDeliveryConfig').mockImplementation(async (...args) => {
1302
+ if (args[0] === root &&
1303
+ contender === undefined &&
1304
+ !existsSync(writer) &&
1305
+ sourcePhaseRecords(root).some((record) => record.status === 'intent')) {
1306
+ contender = spawnSync(process.execPath, [
1307
+ '--input-type=module',
1308
+ '-e',
1309
+ `import {writeFileSync} from 'node:fs';
1310
+ import {withRuntimeSetupWriter} from ${JSON.stringify(new URL('./verification.js', import.meta.url).href)};
1311
+ await withRuntimeSetupWriter(${JSON.stringify(row.path)},async()=>{
1312
+ writeFileSync(${JSON.stringify(metadata)},'competing writer changed completion bytes');
1313
+ });`,
1314
+ ], { encoding: 'utf8', timeout: 10_000, maxBuffer: 64 * 1024 });
1315
+ }
1316
+ return originalLoad(...args);
1317
+ });
1318
+ try {
1319
+ const { withIssueSourcePhase } = await import('./agent.js');
1320
+ const phase = withIssueSourcePhase(input, async (context) => {
1321
+ mkdirSync(outputRoot);
1322
+ writeFileSync(metadata, '{}');
1323
+ for (let index = 0; index < input.commands.length; index++)
1324
+ await context.run(index);
1325
+ if (outcome === 'failed-quiescent')
1326
+ throw new Error('quiescent callback failure');
1327
+ });
1328
+ if (outcome === 'complete')
1329
+ await phase;
1330
+ else
1331
+ await assert.rejects(phase, /failed-quiescent/u);
1332
+ assert.ok(contender, 'competing process attempted entry after writer release and before terminal seal');
1333
+ assert.equal(contender.error, undefined);
1334
+ assert.equal(contender.status, 1);
1335
+ assert.match(String(contender.stderr), /unsealed source phase/u);
1336
+ assert.equal(readFileSync(metadata, 'utf8'), '{}');
1337
+ assert.equal(sourcePhaseRecords(root)[0]?.status, outcome);
1338
+ const afterSeal = spawnSync(process.execPath, [
1339
+ '--input-type=module',
1340
+ '-e',
1341
+ `import {withRuntimeSetupWriter} from ${JSON.stringify(new URL('./verification.js', import.meta.url).href)};
1342
+ await withRuntimeSetupWriter(${JSON.stringify(row.path)},async()=>console.log('writer entered after seal'));`,
1343
+ ], { encoding: 'utf8', timeout: 10_000, maxBuffer: 64 * 1024 });
1344
+ assert.equal(afterSeal.error, undefined);
1345
+ assert.equal(afterSeal.status, 0, String(afterSeal.stderr));
1346
+ assert.match(String(afterSeal.stdout), /writer entered after seal/u);
1347
+ }
1348
+ finally {
1349
+ inspectRelease.mockRestore();
1350
+ rmSync(root, { recursive: true, force: true });
1351
+ }
1352
+ }
1353
+ }, 30_000);
1354
+ test('supported issue source phase corrects invalid reconciliation while retaining every rejected charge', async () => {
1355
+ for (const initial of ['rejected-before-work', 'failed-quiescent']) {
1356
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1357
+ const graph = input.commandGraphDigest;
1358
+ try {
1359
+ const { withIssueSourcePhase } = await import('./agent.js');
1360
+ if (initial === 'rejected-before-work')
1361
+ input.commandGraphDigest = digestValue('initial invalid graph');
1362
+ await assert.rejects(withIssueSourcePhase(input, async () => {
1363
+ if (initial === 'rejected-before-work')
1364
+ assert.fail('invalid graph cannot enter callback');
1365
+ mkdirSync(outputRoot);
1366
+ writeFileSync(join(outputRoot, 'retained.bin'), Buffer.alloc(40 * 1024));
1367
+ throw new Error('original quiescent failure');
1368
+ }), initial === 'rejected-before-work' ? /binding changed/u : /failed-quiescent/u);
1369
+ const original = sourcePhaseRecords(root)[0];
1370
+ input.commandGraphDigest = graph;
1371
+ const reconciliation = {
1372
+ phaseId: String(original.phaseId),
1373
+ recordId: String(original.recordId),
1374
+ authorizationDigest: input.authorization.digest,
1375
+ ...(original.baseline === undefined
1376
+ ? {}
1377
+ : { baselineId: original.baseline.baselineId }),
1378
+ };
1379
+ for (const invalid of ['missing', 'phase', 'record', 'authorization']) {
1380
+ const caller = join(root, '.git', `source-phase-invalid-${invalid}.mjs`);
1381
+ writeFileSync(caller, `// frozen ${invalid} retry caller\n`);
1382
+ input.caller = { path: caller, digest: sha256(caller) };
1383
+ if (invalid === 'missing')
1384
+ delete input.reconciliation;
1385
+ else
1386
+ input.reconciliation = {
1387
+ ...reconciliation,
1388
+ ...(invalid === 'phase' ? { phaseId: digestValue('wrong predecessor phase') } : {}),
1389
+ ...(invalid === 'record' ? { recordId: digestValue('wrong predecessor record') } : {}),
1390
+ ...(invalid === 'authorization' ? { authorizationDigest: digestValue('wrong retry authorization') } : {}),
1391
+ };
1392
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('invalid reconciliation cannot enter callback')), /reconciliation|predecessor/u);
1393
+ }
1394
+ const retained = sourcePhaseRecords(root);
1395
+ assert.equal(retained.length, 5);
1396
+ assert.equal(retained.filter((record) => record.status === 'rejected-before-work').length, initial === 'rejected-before-work' ? 5 : 4);
1397
+ input.reconciliation = reconciliation;
1398
+ const completed = await withIssueSourcePhase(input, async (context) => {
1399
+ mkdirSync(outputRoot, { recursive: true });
1400
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
1401
+ for (let index = 0; index < input.commands.length; index++)
1402
+ await context.run(index);
1403
+ });
1404
+ const finalRecords = sourcePhaseRecords(root);
1405
+ const complete = finalRecords.find((record) => record.recordId === completed.recordId);
1406
+ const bootstrap = retained.reduce((sum, record) => sum + Number(record.bootstrapBytes), 0);
1407
+ assert.ok(Number(complete.result.newOutputBytes) >=
1408
+ bootstrap + (initial === 'failed-quiescent' ? 40 * 1024 : 0));
1409
+ for (const record of retained)
1410
+ assert.deepEqual(finalRecords.find((current) => current.phaseId === record.phaseId), record, 'rejected and failed evidence stays immutable');
1411
+ delete input.reconciliation;
1412
+ const nextCaller = join(root, '.git', 'source-phase-independent-next.mjs');
1413
+ writeFileSync(nextCaller, '// independent later authorized graph\n');
1414
+ input.caller = { path: nextCaller, digest: sha256(nextCaller) };
1415
+ await withIssueSourcePhase(input, async (context) => {
1416
+ for (let index = 0; index < input.commands.length; index++)
1417
+ await context.run(index);
1418
+ });
1419
+ }
1420
+ finally {
1421
+ rmSync(root, { recursive: true, force: true });
1422
+ }
1423
+ }
1424
+ }, 40_000);
1425
+ test('supported issue source phase cannot derive a larger allocation or changed roots from rejected siblings', async () => {
1426
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1427
+ input.bounds.maxNewOutputBytes = 64 * 1024;
1428
+ input.bounds.minFreeDiskBytes = 2;
1429
+ const originalBounds = structuredClone(input.bounds);
1430
+ const graph = input.commandGraphDigest;
1431
+ const replacementRoot = join(root, '.git', 'replacement-source-output');
1432
+ try {
1433
+ const { withIssueSourcePhase } = await import('./agent.js');
1434
+ input.commandGraphDigest = digestValue('initial rejected graph with original allocation');
1435
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('invalid graph')), /binding changed/u);
1436
+ const original = sourcePhaseRecords(root)[0];
1437
+ input.commandGraphDigest = graph;
1438
+ const caller = join(root, '.git', 'source-phase-increased-request.mjs');
1439
+ writeFileSync(caller, '// rejected request cannot grant larger bounds\n');
1440
+ input.caller = { path: caller, digest: sha256(caller) };
1441
+ input.bounds = {
1442
+ maxAggregateRssBytes: 2 * originalBounds.maxAggregateRssBytes,
1443
+ maxNewOutputBytes: 1024 * 1024,
1444
+ minFreeDiskBytes: 1,
1445
+ outputRoots: [replacementRoot, join(root, '.git', 'ai-delivery')],
1446
+ };
1447
+ input.completionArtifacts = [join(replacementRoot, 'metadata.json')];
1448
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('missing reconciliation cannot enter')), /reconciliation|original.*allowance/u);
1449
+ const sibling = sourcePhaseRecords(root).find((record) => record.phaseId !== original.phaseId);
1450
+ input.reconciliation = {
1451
+ phaseId: String(sibling.phaseId),
1452
+ recordId: String(sibling.recordId),
1453
+ authorizationDigest: input.authorization.digest,
1454
+ };
1455
+ let entered = false;
1456
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
1457
+ entered = true;
1458
+ mkdirSync(replacementRoot);
1459
+ writeFileSync(join(replacementRoot, 'metadata.json'), '{}');
1460
+ writeFileSync(join(replacementRoot, 'beyond-original-allowance.bin'), Buffer.alloc(96 * 1024));
1461
+ for (let index = 0; index < input.commands.length; index++)
1462
+ await context.run(index);
1463
+ }), /original.*allowance|original output baseline/u);
1464
+ assert.equal(entered, false, 'a rejected sibling cannot authorize original-bound increases or new roots');
1465
+ input.bounds = structuredClone(originalBounds);
1466
+ input.completionArtifacts = [join(outputRoot, 'metadata.json')];
1467
+ const completed = await withIssueSourcePhase(input, async (context) => {
1468
+ mkdirSync(outputRoot);
1469
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
1470
+ for (let index = 0; index < input.commands.length; index++)
1471
+ await context.run(index);
1472
+ });
1473
+ const records = sourcePhaseRecords(root);
1474
+ for (const record of records)
1475
+ assert.deepEqual(record.allocation, originalBounds);
1476
+ assert.equal(records.find((record) => record.recordId === completed.recordId)?.status, 'complete');
1477
+ assert.deepEqual(records.find((record) => record.recordId === original.recordId), original);
1478
+ assert.equal(existsSync(replacementRoot), false);
1479
+ }
1480
+ finally {
1481
+ rmSync(root, { recursive: true, force: true });
1482
+ }
1483
+ });
1484
+ test('supported issue source phase charges invalid attempts against the original cumulative allowance', async () => {
1485
+ const { root, input } = await sourcePhaseFixture();
1486
+ input.bounds.maxNewOutputBytes = 32 * 1024;
1487
+ const originalBounds = structuredClone(input.bounds);
1488
+ try {
1489
+ const { withIssueSourcePhase } = await import('./agent.js');
1490
+ input.commandGraphDigest = digestValue('invalid frozen graph');
1491
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('invalid graph')), /binding changed/u);
1492
+ input.bounds.maxNewOutputBytes = 1024 * 1024;
1493
+ let exhausted;
1494
+ for (let attempt = 0; attempt < 12; attempt++) {
1495
+ const caller = join(root, '.git', `source-phase-missing-predecessor-${String(attempt)}.mjs`);
1496
+ writeFileSync(caller, `// immutable invalid retry ${String(attempt)}\n`);
1497
+ input.caller = { path: caller, digest: sha256(caller) };
1498
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('invalid retry cannot enter callback')), /reconciliation|unresolved|original.*allowance/u);
1499
+ const record = sourcePhaseRecords(root).find((value) => value.binding?.caller?.path === caller);
1500
+ if (record.status === 'unresolved') {
1501
+ exhausted = record;
1502
+ break;
1503
+ }
1504
+ }
1505
+ assert.ok(exhausted, 'cumulative rejected bootstrap must exhaust the original allocation before twelve resets');
1506
+ assert.deepEqual(exhausted.allocation, originalBounds);
1507
+ assert.match(exhausted.failure.accounting, /original output allowance/u);
1508
+ assert.equal(exhausted.result, undefined);
1509
+ const count = sourcePhaseRecords(root).length;
1510
+ const caller = join(root, '.git', 'source-phase-after-exhaustion.mjs');
1511
+ writeFileSync(caller, '// no new persistent phase metadata after unresolved exhaustion\n');
1512
+ input.caller = { path: caller, digest: sha256(caller) };
1513
+ await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('unresolved allowance cannot admit more work')), /[Uu]nresolved source phase/u);
1514
+ assert.equal(sourcePhaseRecords(root).length, count);
1515
+ }
1516
+ finally {
1517
+ rmSync(root, { recursive: true, force: true });
1518
+ }
1519
+ });
1520
+ test('supported issue source phase refuses undefined callback rejection after successful commands', async () => {
1521
+ const { root, outputRoot, input } = await sourcePhaseFixture();
1522
+ try {
1523
+ const { withIssueSourcePhase } = await import('./agent.js');
1524
+ await assert.rejects(withIssueSourcePhase(input, async (context) => {
1525
+ mkdirSync(outputRoot);
1526
+ writeFileSync(join(outputRoot, 'metadata.json'), '{}');
1527
+ for (let index = 0; index < input.commands.length; index++)
1528
+ await context.run(index);
1529
+ await Promise.reject(undefined);
1530
+ }), /failed-quiescent/u);
1531
+ const record = sourcePhaseRecords(root)[0];
1532
+ assert.equal(record.status, 'failed-quiescent');
1533
+ assert.equal(record.commands.length, 3);
1534
+ assert.equal(record.failure.operation, 'undefined');
1535
+ assert.equal(record.result, undefined);
1536
+ }
1537
+ finally {
1538
+ rmSync(root, { recursive: true, force: true });
1539
+ }
1540
+ });
372
1541
  test('published package and bundled plugin agree on the admission version', () => {
373
1542
  const packageManifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
374
1543
  const pluginManifest = JSON.parse(readFileSync(new URL('../plugins/ai-delivery/.claude-plugin/plugin.json', import.meta.url), 'utf8'));
@@ -1458,6 +2627,126 @@ test('cancelling verification stops its owned descendant and preserves an unrela
1458
2627
  rmSync(root, { recursive: true, force: true });
1459
2628
  }
1460
2629
  });
2630
+ test.skipIf(process.platform !== 'darwin').each(['exited group', 'live root', 'live group member'])('owned process cleanup rechecks EPERM for %s', async (disposition) => {
2631
+ const output = realpathSync(mkdtempSync(join(tmpdir(), 'ad-group-exit-')));
2632
+ const marker = join(output, 'processes'), release = join(output, 'release');
2633
+ const { root } = await fixture({
2634
+ firstStageScript: `const fs=require('node:fs');const {spawn}=require('node:child_process');
2635
+ const descendant=${JSON.stringify(disposition)}==='live group member'?spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'}):undefined;
2636
+ fs.writeFileSync(${JSON.stringify(marker)},JSON.stringify([process.pid,...(descendant?[descendant.pid]:[])]));
2637
+ setInterval(()=>{if(fs.existsSync(${JSON.stringify(release)}))process.exit(0);},10);`,
2638
+ });
2639
+ const originalKill = process.kill.bind(process);
2640
+ const owned = new Map();
2641
+ const unrelated = spawn(process.execPath, ['-e', 'setInterval(()=>{},1000)'], { stdio: 'ignore' });
2642
+ const identity = (pid) => spawnSync('/bin/ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8' }).stdout.trim();
2643
+ const unrelatedIdentity = identity(unrelated.pid);
2644
+ let restoreKill;
2645
+ let permissionExit;
2646
+ const controller = new AbortController();
2647
+ let run;
2648
+ try {
2649
+ const row = await prepareIssueWorktree({
2650
+ baseRef: 'main',
2651
+ identity: 'synthetic-author',
2652
+ issueNumber: 17,
2653
+ repoRoot: root,
2654
+ });
2655
+ writeFileSync(join(row.path, 'change.txt'), 'group exit\n');
2656
+ git(row.path, 'add', 'change.txt');
2657
+ git(row.path, 'commit', '-qm', 'synthetic group exit');
2658
+ run = verifyIssue({ issueNumber: 17, repoRoot: row.path, signal: controller.signal });
2659
+ const rejection = assert.rejects(run, (error) => {
2660
+ assert.match(String(error), /cancelled/u);
2661
+ if (disposition === 'exited group')
2662
+ assert.doesNotMatch(String(error), /cleanup failed/u);
2663
+ else
2664
+ assert.match(String(error), /Owned process cleanup failed \(synthetic live EPERM\)/u);
2665
+ return true;
2666
+ });
2667
+ const readyDeadline = Date.now() + 2_000;
2668
+ while (!existsSync(marker) && Date.now() < readyDeadline)
2669
+ await new Promise((resolve) => setTimeout(resolve, 10));
2670
+ assert.equal(existsSync(marker), true);
2671
+ const pids = JSON.parse(readFileSync(marker, 'utf8'));
2672
+ for (const pid of pids) {
2673
+ const birth = identity(pid);
2674
+ assert.ok(birth);
2675
+ owned.set(pid, birth);
2676
+ }
2677
+ const leader = pids[0];
2678
+ let observedEperm = false;
2679
+ const kill = vi.spyOn(process, 'kill').mockImplementation((pid, signal) => {
2680
+ assert.notEqual(pid, unrelated.pid);
2681
+ if (pid !== -leader || signal !== 'SIGKILL')
2682
+ return originalKill(pid, signal);
2683
+ if (disposition !== 'live root') {
2684
+ writeFileSync(release, 'exit');
2685
+ const exitDeadline = Date.now() + 2_000;
2686
+ for (;;) {
2687
+ assert.equal(identity(leader), owned.get(leader));
2688
+ const state = spawnSync('/bin/ps', ['-p', String(leader), '-o', 'stat='], {
2689
+ encoding: 'utf8',
2690
+ }).stdout.trim();
2691
+ if (state.startsWith('Z'))
2692
+ break;
2693
+ assert.ok(Date.now() < exitDeadline, 'owned leader did not exit at the signal boundary');
2694
+ }
2695
+ }
2696
+ if (disposition === 'exited group') {
2697
+ try {
2698
+ return originalKill(pid, signal);
2699
+ }
2700
+ catch (error) {
2701
+ assert.equal(error.code, 'EPERM');
2702
+ observedEperm = true;
2703
+ throw error;
2704
+ }
2705
+ }
2706
+ if (disposition === 'live group member') {
2707
+ const member = pids[1];
2708
+ assert.equal(identity(member), owned.get(member));
2709
+ assert.doesNotMatch(spawnSync('/bin/ps', ['-p', String(member), '-o', 'stat='], { encoding: 'utf8' }).stdout, /^\s*Z/u);
2710
+ }
2711
+ observedEperm = true;
2712
+ if (disposition === 'live root')
2713
+ permissionExit = setTimeout(() => writeFileSync(release, 'exit'), 500);
2714
+ throw Object.assign(new Error('synthetic live EPERM'), { code: 'EPERM' });
2715
+ });
2716
+ restoreKill = () => kill.mockRestore();
2717
+ controller.abort();
2718
+ await rejection;
2719
+ assert.equal(observedEperm, true);
2720
+ assert.equal(identity(unrelated.pid), unrelatedIdentity);
2721
+ assert.equal(existsSync(join(root, '.git', 'ai-delivery', 'verification@1', 'stages', 'check')), false);
2722
+ }
2723
+ finally {
2724
+ restoreKill?.();
2725
+ clearTimeout(permissionExit);
2726
+ controller.abort();
2727
+ for (const [pid, birth] of owned) {
2728
+ if (identity(pid) === birth) {
2729
+ try {
2730
+ originalKill(pid, 'SIGKILL');
2731
+ }
2732
+ catch (error) {
2733
+ assert.equal(error.code, 'ESRCH');
2734
+ }
2735
+ }
2736
+ }
2737
+ if (unrelated.pid !== undefined && identity(unrelated.pid) === unrelatedIdentity)
2738
+ originalKill(unrelated.pid, 'SIGKILL');
2739
+ await run?.catch(() => undefined);
2740
+ await new Promise((resolve) => {
2741
+ if (unrelated.exitCode !== null || unrelated.signalCode !== null)
2742
+ resolve();
2743
+ else
2744
+ unrelated.once('close', () => resolve());
2745
+ });
2746
+ rmSync(root, { recursive: true, force: true });
2747
+ rmSync(output, { recursive: true, force: true });
2748
+ }
2749
+ });
1461
2750
  test.each([
1462
2751
  { name: 'nonzero exit', terminate: 'process.exit(7)', expected: /exit 7/u },
1463
2752
  { name: 'signal', terminate: "process.kill(process.pid,'SIGTERM')", expected: /signal SIGTERM/u },
@@ -4992,7 +6281,8 @@ test.each([
4992
6281
  fs.symlinkSync('./other', ${JSON.stringify(intermediate)});
4993
6282
  }
4994
6283
  if (${JSON.stringify(mode)} === 'chain-cycle') {
4995
- fs.unlinkSync(${JSON.stringify(intermediate)}); fs.symlinkSync('./cli', ${JSON.stringify(intermediate)});
6284
+ const replacement = ${JSON.stringify(join(output, 'next-intermediate'))};
6285
+ fs.symlinkSync('./cli', replacement); fs.renameSync(replacement, ${JSON.stringify(intermediate)});
4996
6286
  }
4997
6287
  for (let unit = 0; unit < 32; unit++) {
4998
6288
  fs.appendFileSync(${JSON.stringify(join(second, 'work'))}, 'unit\\n');
@@ -5020,13 +6310,17 @@ test.each([
5020
6310
  ? /unsupported file/u
5021
6311
  : mode === 'permission'
5022
6312
  ? /readdir failed.*EACCES/u
5023
- : mode === 'cycle' || mode === 'chain-cycle'
6313
+ : mode === 'cycle'
5024
6314
  ? /ELOOP/u
5025
- : /identity changed/u);
6315
+ : mode === 'chain-cycle'
6316
+ ? /ELOOP|alias identity changed/u
6317
+ : /identity changed/u);
5026
6318
  assert.match(message, /First filesystem observation:.*ENOENT.*Last filesystem observation:/u);
5027
6319
  assert.doesNotMatch(message, /cleanup failed/u);
5028
6320
  return true;
5029
6321
  });
6322
+ if (mode === 'cycle' || mode === 'chain-cycle')
6323
+ assert.throws(() => realpathSync(alias), (error) => error.code === 'ELOOP');
5030
6324
  assert.ok(observations.some((line) => line.includes('ENOENT')));
5031
6325
  assert.equal(observations.some((line) => line.includes('"state":"completed"')), false);
5032
6326
  assert.equal(readFileSync(join(second, 'unrelated'), 'utf8'), 'preserved');
@@ -5156,6 +6450,91 @@ test.each(['pass', 'chain-pass', 'command-error', 'cancel', 'persistent', 'limit
5156
6450
  rmSync(output, { recursive: true, force: true });
5157
6451
  }
5158
6452
  });
6453
+ test.each(['pass', 'limit', 'command-error'])('validated alias deletion during identity readback keeps fresh slow-scan %s evidence', async (mode) => {
6454
+ const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-readback-deletion-')));
6455
+ const { root } = await fixture();
6456
+ const alias = join(output, 'alias'), target = join(output, 'target'), release = join(output, 'release'), fresh = join(output, 'z-new-output');
6457
+ writeFileSync(target, 'target');
6458
+ writeFileSync(join(output, 'unrelated'), 'preserved');
6459
+ symlinkSync('./target', alias);
6460
+ const fs = (await import('node:fs')).default;
6461
+ const actualLstat = fs.lstatSync;
6462
+ const now = Date.now.bind(Date);
6463
+ let removedDuringVisit = false, scannedFreshOutput = false, offset = 0;
6464
+ const clock = vi.spyOn(Date, 'now').mockImplementation(() => now() + offset);
6465
+ const scan = vi.spyOn(fs, 'lstatSync').mockImplementation((path, options) => {
6466
+ if (path === fresh && removedDuringVisit && !scannedFreshOutput) {
6467
+ scannedFreshOutput = true;
6468
+ // Inject scan duration only; removal, physical inventory and byte accounting are real.
6469
+ offset += 1500;
6470
+ }
6471
+ return actualLstat(path, options);
6472
+ });
6473
+ syncBuiltinESMExports();
6474
+ const progress = vi.spyOn(process.stderr, 'write').mockImplementation((line) => {
6475
+ if (!removedDuringVisit && String(line).includes('broken or cyclic symbolic link')) {
6476
+ writeFileSync(release, 'remove owned alias');
6477
+ const until = now() + 5000;
6478
+ for (;;) {
6479
+ try {
6480
+ actualLstat(alias);
6481
+ }
6482
+ catch (error) {
6483
+ if (error.code !== 'ENOENT')
6484
+ throw error;
6485
+ removedDuringVisit = true;
6486
+ break;
6487
+ }
6488
+ if (now() >= until)
6489
+ throw new Error('Owned alias deletion handshake failed.');
6490
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
6491
+ }
6492
+ }
6493
+ return true;
6494
+ });
6495
+ const script = `(async () => {
6496
+ const fs = require('node:fs');
6497
+ fs.writeFileSync(${JSON.stringify(fresh)}, 'x'.repeat(${mode === 'limit' ? 8192 : 512}));
6498
+ fs.unlinkSync(${JSON.stringify(target)});
6499
+ while (!fs.existsSync(${JSON.stringify(release)})) await new Promise(resolve => setTimeout(resolve, 5));
6500
+ fs.unlinkSync(${JSON.stringify(alias)});
6501
+ ${mode === 'command-error' ? "console.error('synthetic deletion command failure'); process.exitCode = 7;" : ''}
6502
+ })().catch(error => { console.error(error); process.exitCode = 1; });`;
6503
+ try {
6504
+ const { withRuntimeSetupWriter } = await import('./verification.js');
6505
+ const run = withRuntimeSetupWriter(root, (runner) => runner.run([process.execPath, '-e', script], {
6506
+ maxAggregateRssBytes: 1_000_000_000,
6507
+ minFreeDiskBytes: 1,
6508
+ maxNewOutputBytes: 4096,
6509
+ outputRoots: [output],
6510
+ }));
6511
+ if (mode === 'pass')
6512
+ await run;
6513
+ else
6514
+ await assert.rejects(run, (error) => {
6515
+ assert.match(String(error), mode === 'limit' ? /Positive new filesystem output 8192 exceeded limit 4096/u : /Command exit 7/u);
6516
+ assert.match(String(error), /Command output sha256:/u);
6517
+ assert.doesNotMatch(String(error), /observation remained unavailable|cleanup failed/u);
6518
+ return true;
6519
+ });
6520
+ assert.ok(removedDuringVisit);
6521
+ assert.ok(scannedFreshOutput);
6522
+ assert.equal(readdirSync(output).includes('alias'), false);
6523
+ assert.equal(readFileSync(fresh).length, mode === 'limit' ? 8192 : 512);
6524
+ assert.equal(readFileSync(join(output, 'unrelated'), 'utf8'), 'preserved');
6525
+ await withRuntimeSetupWriter(root, async (runner) => {
6526
+ runner.assertQuiescent();
6527
+ });
6528
+ }
6529
+ finally {
6530
+ progress.mockRestore();
6531
+ scan.mockRestore();
6532
+ syncBuiltinESMExports();
6533
+ clock.mockRestore();
6534
+ rmSync(root, { recursive: true, force: true });
6535
+ rmSync(output, { recursive: true, force: true });
6536
+ }
6537
+ });
5159
6538
  test('injected first failed scan duration does not consume the subsequent observation stall window', async () => {
5160
6539
  const output = realpathSync(mkdtempSync(join(tmpdir(), 'ai-delivery-scan-duration-')));
5161
6540
  const { root } = await fixture();