@aviaratech/ai-delivery 0.3.18 → 0.3.20
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 +34 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/deliveryConfig.d.ts +1 -0
- package/dist/config/deliveryConfig.js +3 -0
- package/dist/config/deliveryConfig.js.map +1 -1
- package/dist/config/deliveryConfig.test.js +106 -0
- package/dist/config/deliveryConfig.test.js.map +1 -1
- package/dist/github/client.d.ts +3 -2
- package/dist/github/client.js +35 -15
- package/dist/github/client.js.map +1 -1
- package/dist/issue.d.ts +1 -0
- package/dist/issue.js +5 -1
- package/dist/issue.js.map +1 -1
- package/dist/lifecycle.test.js +313 -4
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +7 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pr.d.ts +4 -1
- package/dist/pr.js +100 -5
- package/dist/pr.js.map +1 -1
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +71 -25
- package/dist/setup.js.map +1 -1
- package/dist/setup.test.js +342 -4
- package/dist/setup.test.js.map +1 -1
- package/dist/verification.d.ts +9 -2
- package/dist/verification.js +215 -66
- package/dist/verification.js.map +1 -1
- package/dist/worktreeTransition.test.js +13 -0
- 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
|
@@ -931,11 +931,197 @@ test('supported issue source phase runs fixture tests, an enabled-hook commit an
|
|
|
931
931
|
assert.equal(git(row.path, 'rev-parse', 'HEAD^'), input.source.head);
|
|
932
932
|
assert.equal(receipt.source.indexTree, input.source.indexTree);
|
|
933
933
|
assert.equal(readFileSync(join(outputRoot, 'hook-count.txt'), 'utf8'), 'enabled\n');
|
|
934
|
+
const { environment, ...legacyBinding } = input;
|
|
935
|
+
assert.equal(receipt.phaseId, digestValue({ ...legacyBinding, environmentDigest: environment.digest }));
|
|
936
|
+
assert.equal(Object.hasOwn(input.source.effect, 'configDigest'), false);
|
|
937
|
+
const completedPath = join(gitCommonDir(root), 'ai-delivery', 'receipts', 'source-phase@1', digestValue(row.path).slice(7), `${receipt.phaseId.slice(7)}.json`);
|
|
938
|
+
const completedBytes = readFileSync(completedPath);
|
|
939
|
+
const completed = JSON.parse(completedBytes.toString());
|
|
940
|
+
assert.equal(Object.hasOwn(completed.binding.source.effect, 'configDigest'), false);
|
|
934
941
|
const reused = await withIssueSourcePhase(input, async () => {
|
|
935
942
|
entered += 1;
|
|
936
943
|
});
|
|
937
944
|
assert.equal(reused.recordId, receipt.recordId);
|
|
938
945
|
assert.equal(entered, 1);
|
|
946
|
+
assert.deepEqual(readFileSync(completedPath), completedBytes);
|
|
947
|
+
}
|
|
948
|
+
finally {
|
|
949
|
+
rmSync(root, { recursive: true, force: true });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
async function sourceConfigurationTransitionFixture() {
|
|
953
|
+
const result = await sourcePhaseFixture({ personalAuthor: true, custodianIdentity: 'historical-builder' });
|
|
954
|
+
const { root, row, outputRoot, input } = result;
|
|
955
|
+
const policy = join(row.path, 'policy.mjs');
|
|
956
|
+
const original = readFileSync(policy, 'utf8');
|
|
957
|
+
const revised = `${original}\n// Declared candidate policy revision.\n`;
|
|
958
|
+
writeFileSync(policy, revised);
|
|
959
|
+
const finalConfiguration = await loadDeliveryConfig(row.path);
|
|
960
|
+
const finalPolicyDigest = sha256(policy);
|
|
961
|
+
git(row.path, 'add', 'policy.mjs');
|
|
962
|
+
const tree = git(row.path, 'write-tree');
|
|
963
|
+
writeFileSync(policy, original);
|
|
964
|
+
git(row.path, 'add', 'policy.mjs');
|
|
965
|
+
assert.equal(git(row.path, 'write-tree'), input.source.indexTree);
|
|
966
|
+
assert.equal(git(row.path, 'status', '--porcelain'), '');
|
|
967
|
+
assert.equal((await loadDeliveryConfig(row.path)).configDigest, input.source.configDigest);
|
|
968
|
+
assert.notEqual(finalConfiguration.configDigest, input.source.configDigest);
|
|
969
|
+
assert.notEqual(finalConfiguration.configDigest, finalPolicyDigest);
|
|
970
|
+
const effect = {
|
|
971
|
+
kind: 'commitOnce',
|
|
972
|
+
parent: input.source.head,
|
|
973
|
+
tree,
|
|
974
|
+
configDigest: finalConfiguration.configDigest,
|
|
975
|
+
};
|
|
976
|
+
input.source.effect = effect;
|
|
977
|
+
const hook = join(root, '.git', 'source-phase-hooks', 'pre-commit');
|
|
978
|
+
mkdirSync(dirname(hook));
|
|
979
|
+
writeFileSync(hook, `#!/bin/sh\nprintf 'enabled\\n' >> ${JSON.stringify(join(outputRoot, 'hook-count.txt'))}\n`, {
|
|
980
|
+
mode: 0o700,
|
|
981
|
+
});
|
|
982
|
+
const gitPath = realpathSync(execFileSync('/usr/bin/which', ['git'], { encoding: 'utf8' }).trim());
|
|
983
|
+
input.inputs = [hook, gitPath].map((path) => ({ path, digest: sha256(path) }));
|
|
984
|
+
const transition = `const fs=require('node:fs'),c=require('node:child_process');
|
|
985
|
+
fs.writeFileSync(${JSON.stringify(policy)},${JSON.stringify(revised)});
|
|
986
|
+
const git=(...args)=>c.execFileSync(${JSON.stringify(gitPath)},args,{stdio:'inherit'});
|
|
987
|
+
git('add','policy.mjs');
|
|
988
|
+
git('-c',${JSON.stringify(`core.hooksPath=${dirname(hook)}`)},'commit','-m','Reviewed candidate policy transition');`;
|
|
989
|
+
input.commands[1].argv = [realpathSync(process.execPath), '-e', transition];
|
|
990
|
+
input.commandGraphDigest = digestValue(input.commands);
|
|
991
|
+
return { ...result, policy, original, revised, effect, transition };
|
|
992
|
+
}
|
|
993
|
+
test('supported issue source phase binds initial and final resolved candidate configurations across one normal-hook commit', async () => {
|
|
994
|
+
const { root, row, outputRoot, input, effect } = await sourceConfigurationTransitionFixture();
|
|
995
|
+
try {
|
|
996
|
+
const { withIssueSourcePhase } = await import('./agent.js');
|
|
997
|
+
let entered = 0;
|
|
998
|
+
const receipt = await withIssueSourcePhase(input, async (context) => {
|
|
999
|
+
entered += 1;
|
|
1000
|
+
assert.equal(context.source.configDigest, input.source.configDigest);
|
|
1001
|
+
mkdirSync(outputRoot);
|
|
1002
|
+
writeFileSync(join(outputRoot, 'metadata.json'), '{}');
|
|
1003
|
+
for (let index = 0; index < input.commands.length; index++)
|
|
1004
|
+
await context.run(index);
|
|
1005
|
+
});
|
|
1006
|
+
assert.equal(receipt.source.configDigest, effect.configDigest);
|
|
1007
|
+
assert.equal(receipt.source.indexTree, effect.tree);
|
|
1008
|
+
assert.equal(git(row.path, 'rev-list', '--parents', '-n', '1', 'HEAD'), `${receipt.source.head} ${input.source.head}`);
|
|
1009
|
+
assert.equal(git(row.path, 'status', '--porcelain'), '');
|
|
1010
|
+
assert.equal(git(root, 'rev-parse', 'HEAD'), input.controller.head);
|
|
1011
|
+
assert.equal((await loadDeliveryConfig(root)).configDigest, input.controller.configDigest);
|
|
1012
|
+
assert.equal(readFileSync(join(outputRoot, 'hook-count.txt'), 'utf8'), 'enabled\n');
|
|
1013
|
+
const completed = sourcePhaseRecords(root).find((record) => record.phaseId === receipt.phaseId);
|
|
1014
|
+
assert.deepEqual(completed?.binding?.source?.effect, effect);
|
|
1015
|
+
const reused = await withIssueSourcePhase(input, async () => {
|
|
1016
|
+
entered += 1;
|
|
1017
|
+
});
|
|
1018
|
+
assert.equal(reused.recordId, receipt.recordId);
|
|
1019
|
+
assert.deepEqual(sourcePhaseRecords(root).find((record) => record.phaseId === receipt.phaseId), completed);
|
|
1020
|
+
assert.equal(entered, 1);
|
|
1021
|
+
assert.equal(readFileSync(join(outputRoot, 'hook-count.txt'), 'utf8'), 'enabled\n');
|
|
1022
|
+
for (const digest of ['initial', 'final']) {
|
|
1023
|
+
const changed = structuredClone(input);
|
|
1024
|
+
if (digest === 'initial')
|
|
1025
|
+
changed.source.configDigest = digestValue('different initial configuration');
|
|
1026
|
+
else
|
|
1027
|
+
changed.source.effect = { ...effect, configDigest: digestValue('different final configuration') };
|
|
1028
|
+
await assert.rejects(withIssueSourcePhase(changed, async () => assert.fail('changed binding cannot reuse completion or enter')), /initial source|configuration|reconciliation/u);
|
|
1029
|
+
assert.deepEqual(sourcePhaseRecords(root).find((record) => record.phaseId === receipt.phaseId), completed);
|
|
1030
|
+
assert.equal(readFileSync(join(outputRoot, 'hook-count.txt'), 'utf8'), 'enabled\n');
|
|
1031
|
+
}
|
|
1032
|
+
writeFileSync(join(row.path, 'policy.mjs'), 'export const deliverySettings = {};\n');
|
|
1033
|
+
await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('changed final source cannot reuse')));
|
|
1034
|
+
assert.deepEqual(sourcePhaseRecords(root).find((record) => record.phaseId === receipt.phaseId), completed);
|
|
1035
|
+
}
|
|
1036
|
+
finally {
|
|
1037
|
+
rmSync(root, { recursive: true, force: true });
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
test.each(['preserve', 'invalid digest', 'unknown field'])('supported issue source phase strictly refuses a final configuration declaration with %s', async (invalid) => {
|
|
1041
|
+
const { root, input, effect } = await sourceConfigurationTransitionFixture();
|
|
1042
|
+
const declaration = invalid === 'preserve'
|
|
1043
|
+
? { kind: 'preserve', configDigest: effect.configDigest }
|
|
1044
|
+
: invalid === 'invalid digest'
|
|
1045
|
+
? { ...effect, configDigest: 'invalid' }
|
|
1046
|
+
: { ...effect, finalConfigDigest: effect.configDigest };
|
|
1047
|
+
try {
|
|
1048
|
+
const { withIssueSourcePhase } = await import('./agent.js');
|
|
1049
|
+
await assert.rejects(withIssueSourcePhase({ ...input, source: { ...input.source, effect: declaration } }, async () => assert.fail('invalid declaration cannot enter')));
|
|
1050
|
+
assert.equal(existsSync(join(gitCommonDir(root), 'ai-delivery', 'receipts', 'source-phase@1')), false);
|
|
1051
|
+
}
|
|
1052
|
+
finally {
|
|
1053
|
+
rmSync(root, { recursive: true, force: true });
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
test('supported issue source phase refuses the final configuration at the initial head before work', async () => {
|
|
1057
|
+
const { root, input, policy, revised } = await sourceConfigurationTransitionFixture();
|
|
1058
|
+
writeFileSync(policy, revised);
|
|
1059
|
+
try {
|
|
1060
|
+
const { withIssueSourcePhase } = await import('./agent.js');
|
|
1061
|
+
await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('initial head requires initial configuration')), /configuration/u);
|
|
1062
|
+
}
|
|
1063
|
+
finally {
|
|
1064
|
+
rmSync(root, { recursive: true, force: true });
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
test.each([
|
|
1068
|
+
'omitted digest',
|
|
1069
|
+
'wrong digest',
|
|
1070
|
+
'wrong tree',
|
|
1071
|
+
'wrong parent',
|
|
1072
|
+
'second commit',
|
|
1073
|
+
'dirty final',
|
|
1074
|
+
'controller configuration',
|
|
1075
|
+
'seal configuration',
|
|
1076
|
+
])('supported issue source phase refuses a candidate configuration transition with %s without replay', async (invalid) => {
|
|
1077
|
+
const { root, row, outputRoot, input, effect, transition, policy, original } = await sourceConfigurationTransitionFixture();
|
|
1078
|
+
if (invalid === 'omitted digest') {
|
|
1079
|
+
input.source.effect = { kind: 'commitOnce', parent: effect.parent, tree: effect.tree };
|
|
1080
|
+
}
|
|
1081
|
+
else if (invalid === 'wrong digest') {
|
|
1082
|
+
input.source.effect = { ...effect, configDigest: input.source.configDigest };
|
|
1083
|
+
}
|
|
1084
|
+
else if (invalid === 'wrong tree') {
|
|
1085
|
+
input.source.effect = { ...effect, tree: input.source.indexTree };
|
|
1086
|
+
}
|
|
1087
|
+
else if (invalid === 'wrong parent') {
|
|
1088
|
+
input.source.effect = { ...effect, parent: '1'.repeat(40) };
|
|
1089
|
+
}
|
|
1090
|
+
else if (invalid === 'second commit') {
|
|
1091
|
+
input.commands[1].argv[2] = `${transition}\ngit('commit','--allow-empty','-m','Undeclared second commit');`;
|
|
1092
|
+
}
|
|
1093
|
+
else if (invalid === 'dirty final') {
|
|
1094
|
+
input.commands[1].argv[2] = `${transition}\nfs.writeFileSync('artifact.txt','undeclared dirty final');`;
|
|
1095
|
+
}
|
|
1096
|
+
else if (invalid === 'controller configuration') {
|
|
1097
|
+
input.commands[1].argv[2] = `${transition}\nfs.appendFileSync(${JSON.stringify(join(root, 'policy.mjs'))},'\\n// Undeclared controller change.\\n');`;
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
input.commands[2].argv = [
|
|
1101
|
+
realpathSync(process.execPath),
|
|
1102
|
+
'-e',
|
|
1103
|
+
`require('node:fs').writeFileSync(${JSON.stringify(policy)},${JSON.stringify(original)});`,
|
|
1104
|
+
];
|
|
1105
|
+
}
|
|
1106
|
+
input.commandGraphDigest = digestValue(input.commands);
|
|
1107
|
+
try {
|
|
1108
|
+
const { withIssueSourcePhase } = await import('./agent.js');
|
|
1109
|
+
let entered = 0;
|
|
1110
|
+
await assert.rejects(withIssueSourcePhase(input, async (context) => {
|
|
1111
|
+
entered += 1;
|
|
1112
|
+
mkdirSync(outputRoot);
|
|
1113
|
+
writeFileSync(join(outputRoot, 'metadata.json'), '{}');
|
|
1114
|
+
for (let index = 0; index < input.commands.length; index++)
|
|
1115
|
+
await context.run(index);
|
|
1116
|
+
}), invalid === 'controller configuration' ? /repository admission/u : /configuration|parent, tree/u);
|
|
1117
|
+
assert.equal(entered, 1);
|
|
1118
|
+
assert.notEqual(git(row.path, 'rev-parse', 'HEAD'), input.source.head);
|
|
1119
|
+
assert.equal(sourcePhaseRecords(root)[0]?.status, 'unresolved');
|
|
1120
|
+
const head = git(row.path, 'rev-parse', 'HEAD');
|
|
1121
|
+
const hooks = readFileSync(join(outputRoot, 'hook-count.txt'));
|
|
1122
|
+
await assert.rejects(withIssueSourcePhase(input, async () => assert.fail('failed commit cannot replay')));
|
|
1123
|
+
assert.equal(git(row.path, 'rev-parse', 'HEAD'), head);
|
|
1124
|
+
assert.deepEqual(readFileSync(join(outputRoot, 'hook-count.txt')), hooks);
|
|
939
1125
|
}
|
|
940
1126
|
finally {
|
|
941
1127
|
rmSync(root, { recursive: true, force: true });
|
|
@@ -4567,6 +4753,13 @@ async function syntheticLifecycle(routing) {
|
|
|
4567
4753
|
const historicalActor = routing.committedDescendantContinuation ? 'host-user' : 'synthetic-author[bot]';
|
|
4568
4754
|
let nativePrAuthor = historicalActor;
|
|
4569
4755
|
let nativeClosingIssue = issueNumber;
|
|
4756
|
+
let nativeClosingConnectionEmpty = false;
|
|
4757
|
+
let nativeClosingHasNextPage = false;
|
|
4758
|
+
let nativeRefsBody = `Refs #${String(issueNumber)}`;
|
|
4759
|
+
let nativeTimelinePages;
|
|
4760
|
+
let nativeTimelineFailurePage;
|
|
4761
|
+
let nativeTimelineNextLink = false;
|
|
4762
|
+
let nativeTimelineCalls = 0;
|
|
4570
4763
|
let nativePrRepository;
|
|
4571
4764
|
const continuationComments = [];
|
|
4572
4765
|
const createPayloads = [];
|
|
@@ -4593,6 +4786,8 @@ async function syntheticLifecycle(routing) {
|
|
|
4593
4786
|
node_id: 'PR-23',
|
|
4594
4787
|
title: 'Synthetic change',
|
|
4595
4788
|
html_url: 'https://example.test/pulls/23',
|
|
4789
|
+
url: 'https://api.example.test/repos/example/widget/pulls/23',
|
|
4790
|
+
body: nativeRefsBody,
|
|
4596
4791
|
state: stale ? 'open' : prState,
|
|
4597
4792
|
draft: prDraft,
|
|
4598
4793
|
merged_at: !stale && prState === 'closed' ? mergedAt : null,
|
|
@@ -4602,16 +4797,39 @@ async function syntheticLifecycle(routing) {
|
|
|
4602
4797
|
mergeable_state: 'clean',
|
|
4603
4798
|
user: { login: nativePrAuthor, id: 37, type: routing.committedDescendantContinuation ? 'User' : 'Bot' },
|
|
4604
4799
|
head: { sha: headSha, ref: 'issue/17', ...(nativePrRepository ? { repo: { full_name: nativePrRepository } } : {}) },
|
|
4605
|
-
base: {
|
|
4800
|
+
base: {
|
|
4801
|
+
sha: baseSha,
|
|
4802
|
+
ref: 'main',
|
|
4803
|
+
...(nativePrRepository
|
|
4804
|
+
? { repo: { full_name: nativePrRepository, url: `https://api.example.test/repos/${nativePrRepository}` } }
|
|
4805
|
+
: {}),
|
|
4806
|
+
},
|
|
4807
|
+
});
|
|
4808
|
+
const nativeReference = () => ({
|
|
4809
|
+
event: 'cross-referenced',
|
|
4810
|
+
actor: { login: historicalActor, id: 37, type: 'User' },
|
|
4811
|
+
created_at: new Date(Date.parse(mergedAt) - 60_000).toISOString(),
|
|
4812
|
+
source: {
|
|
4813
|
+
type: 'issue',
|
|
4814
|
+
issue: {
|
|
4815
|
+
number: prNumber,
|
|
4816
|
+
user: { login: historicalActor, id: 37, type: 'User' },
|
|
4817
|
+
repository_url: 'https://api.example.test/repos/example/widget',
|
|
4818
|
+
html_url: 'https://example.test/pulls/23',
|
|
4819
|
+
pull_request: { url: 'https://api.example.test/repos/example/widget/pulls/23' },
|
|
4820
|
+
},
|
|
4821
|
+
},
|
|
4606
4822
|
});
|
|
4607
4823
|
const graphql = async (query, variables = {}) => {
|
|
4608
|
-
if (query.includes('WorktreeTransitionLineage'))
|
|
4824
|
+
if (query.includes('WorktreeTransitionLineage') || query.includes('CommittedContinuationLineage'))
|
|
4609
4825
|
return {
|
|
4610
4826
|
repository: {
|
|
4611
4827
|
pullRequest: {
|
|
4612
4828
|
closingIssuesReferences: {
|
|
4613
|
-
nodes:
|
|
4614
|
-
|
|
4829
|
+
nodes: nativeClosingConnectionEmpty
|
|
4830
|
+
? []
|
|
4831
|
+
: [{ number: nativeClosingIssue, repository: { nameWithOwner: nativePrRepository } }],
|
|
4832
|
+
pageInfo: { ...pageInfo, hasNextPage: nativeClosingHasNextPage },
|
|
4615
4833
|
},
|
|
4616
4834
|
},
|
|
4617
4835
|
},
|
|
@@ -4814,6 +5032,18 @@ async function syntheticLifecycle(routing) {
|
|
|
4814
5032
|
throw new Error(`Unexpected REST route: ${route}`);
|
|
4815
5033
|
},
|
|
4816
5034
|
issues: {
|
|
5035
|
+
listEventsForTimeline: async (input) => {
|
|
5036
|
+
assert.equal(input.issue_number, issueNumber);
|
|
5037
|
+
nativeTimelineCalls += 1;
|
|
5038
|
+
if (input.page === nativeTimelineFailurePage)
|
|
5039
|
+
throw new Error('Synthetic native timeline unavailable');
|
|
5040
|
+
return {
|
|
5041
|
+
headers: nativeTimelineNextLink && input.page === 1
|
|
5042
|
+
? { link: '<https://api.example.test/repos/example/widget/issues/17/timeline?page=2>; rel="next"' }
|
|
5043
|
+
: {},
|
|
5044
|
+
data: nativeTimelinePages?.[input.page - 1] ?? (input.page === 1 ? [nativeReference()] : []),
|
|
5045
|
+
};
|
|
5046
|
+
},
|
|
4817
5047
|
listComments: async () => ({ data: continuationComments }),
|
|
4818
5048
|
getComment: async (input) => ({
|
|
4819
5049
|
data: continuationComments.find((comment) => comment.id === input.comment_id),
|
|
@@ -5675,6 +5905,78 @@ exec "${realGit}" "$@"
|
|
|
5675
5905
|
});
|
|
5676
5906
|
assert.equal(crashIndex, 2);
|
|
5677
5907
|
}
|
|
5908
|
+
if (routing.nativeRefsContinuation) {
|
|
5909
|
+
nativeClosingConnectionEmpty = true;
|
|
5910
|
+
nativeClosingHasNextPage = true;
|
|
5911
|
+
await refuse(/Native PR lineage/u);
|
|
5912
|
+
nativeClosingHasNextPage = false;
|
|
5913
|
+
nativeClosingConnectionEmpty = false;
|
|
5914
|
+
nativeClosingIssue = 18;
|
|
5915
|
+
await refuse(/Native PR lineage/u);
|
|
5916
|
+
nativeClosingIssue = issueNumber;
|
|
5917
|
+
nativeClosingConnectionEmpty = true;
|
|
5918
|
+
for (const body of ['', 'Refs #18', 'Refs #170', 'Refs #17letters', 'Refs #17_extra', 'Mention #17']) {
|
|
5919
|
+
nativeRefsBody = body;
|
|
5920
|
+
await refuse(/Native PR lineage/u);
|
|
5921
|
+
}
|
|
5922
|
+
nativeRefsBody = `Refs #${String(issueNumber)}`;
|
|
5923
|
+
const event = nativeReference();
|
|
5924
|
+
const source = event.source;
|
|
5925
|
+
const sourceIssue = source.issue;
|
|
5926
|
+
for (const invalid of [
|
|
5927
|
+
undefined,
|
|
5928
|
+
{ ...event, actor: { ...event.actor, id: 99 } },
|
|
5929
|
+
{ ...event, actor: { ...event.actor, login: 'different-user' } },
|
|
5930
|
+
{ ...event, actor: { ...event.actor, type: 'Bot' } },
|
|
5931
|
+
{ ...event, created_at: mergedAt },
|
|
5932
|
+
{ ...event, created_at: new Date(Date.parse(mergedAt) + 1000).toISOString() },
|
|
5933
|
+
{ ...event, created_at: 'unknown' },
|
|
5934
|
+
{ ...event, source: { ...source, type: 'pull_request' } },
|
|
5935
|
+
{ ...event, source: { ...source, issue: { ...sourceIssue, number: 24 } } },
|
|
5936
|
+
{
|
|
5937
|
+
...event,
|
|
5938
|
+
source: {
|
|
5939
|
+
...source,
|
|
5940
|
+
issue: { ...sourceIssue, repository_url: 'https://api.example.test/repos/other/widget' },
|
|
5941
|
+
},
|
|
5942
|
+
},
|
|
5943
|
+
{ ...event, source: { ...source, issue: { ...sourceIssue, html_url: 'https://example.test/pulls/24' } } },
|
|
5944
|
+
{
|
|
5945
|
+
...event,
|
|
5946
|
+
source: {
|
|
5947
|
+
...source,
|
|
5948
|
+
issue: {
|
|
5949
|
+
...sourceIssue,
|
|
5950
|
+
pull_request: { url: 'https://api.example.test/repos/example/widget/pulls/24' },
|
|
5951
|
+
},
|
|
5952
|
+
},
|
|
5953
|
+
},
|
|
5954
|
+
{ ...event, source: { ...source, issue: { ...sourceIssue, pull_request: undefined } } },
|
|
5955
|
+
{ ...event, source: { ...source, issue: { ...sourceIssue, user: { ...sourceIssue.user, id: 99 } } } },
|
|
5956
|
+
]) {
|
|
5957
|
+
nativeTimelinePages = [invalid === undefined ? [] : [invalid]];
|
|
5958
|
+
await refuse(/Native PR lineage/u);
|
|
5959
|
+
}
|
|
5960
|
+
nativeTimelinePages = [[event]];
|
|
5961
|
+
nativeTimelineFailurePage = 1;
|
|
5962
|
+
await refuse(/Synthetic native timeline unavailable/u);
|
|
5963
|
+
nativeTimelineFailurePage = 2;
|
|
5964
|
+
nativeTimelineNextLink = true;
|
|
5965
|
+
const before = nativeTimelineCalls;
|
|
5966
|
+
await refuse(/Synthetic native timeline unavailable/u);
|
|
5967
|
+
assert.equal(nativeTimelineCalls - before, 2, 'matching first page cannot hide a missing next page');
|
|
5968
|
+
nativeTimelineFailurePage = undefined;
|
|
5969
|
+
nativeTimelineNextLink = false;
|
|
5970
|
+
nativeTimelinePages = [Array.from({ length: 100 }, () => ({ event: 'commented' })), [event]];
|
|
5971
|
+
unlinkSync(witness.path);
|
|
5972
|
+
try {
|
|
5973
|
+
await refuse(/ownership witness/u);
|
|
5974
|
+
}
|
|
5975
|
+
finally {
|
|
5976
|
+
writeFileSync(witness.path, witness.bytes, { mode: 0o600 });
|
|
5977
|
+
}
|
|
5978
|
+
nativeRefsBody = `Refs #${String(issueNumber)}. Keep the issue open for retirement and supported consumer cutover.`;
|
|
5979
|
+
}
|
|
5678
5980
|
const first = await executeTool('issue_develop', { issueNumber }, execution).catch((error) => error);
|
|
5679
5981
|
assert.ok(first instanceof Error);
|
|
5680
5982
|
assert.match(first.message, /exact native.*acceptance/u);
|
|
@@ -6123,6 +6425,13 @@ test('supported develop reconciles authenticated host custody for an already com
|
|
|
6123
6425
|
assert.ok(uninterrupted.value);
|
|
6124
6426
|
assert.deepEqual(recovered.value, uninterrupted.value);
|
|
6125
6427
|
});
|
|
6428
|
+
test('supported develop preserves native Refs lineage for a committed descendant', { timeout: 0 }, async () => syntheticLifecycle({
|
|
6429
|
+
remote: 'origin',
|
|
6430
|
+
divergentOrigin: false,
|
|
6431
|
+
committedDescendantContinuation: true,
|
|
6432
|
+
nativeRefsContinuation: true,
|
|
6433
|
+
interruptContinuation: false,
|
|
6434
|
+
}));
|
|
6126
6435
|
test('committed descendant checkpoint publication recovers from SIGKILL before and after every rename', { timeout: 0 }, async () => syntheticLifecycle({
|
|
6127
6436
|
remote: 'origin',
|
|
6128
6437
|
divergentOrigin: false,
|