@open-agent-toolkit/cli 0.2.73 → 0.2.74
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +3 -56
- package/assets/docs/cli-utilities/tool-packs.md +4 -4
- package/assets/docs/contributing/code.md +2 -2
- package/assets/docs/contributing/index.md +0 -1
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/reference/troubleshooting.md +17 -40
- package/assets/docs/workflows/projects/artifacts.md +16 -34
- package/assets/docs/workflows/projects/lifecycle.md +20 -19
- package/assets/docs/workflows/skills/explainer-kit.md +180 -440
- package/assets/docs/workflows/skills/index.md +1 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +96 -171
- package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
- package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
- package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
- package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
- package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
- package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
- package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
- package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
- package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
- package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
- package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
- package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
- package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
- package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
- package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
- package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
- package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
- package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
- package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
- package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
- package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
- package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
- package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
- package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
- package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
- package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
- package/assets/skills/oat-project-complete/SKILL.md +168 -349
- package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
- package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
- package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
- package/assets/skills/oat-project-document/SKILL.md +1 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-lite/SKILL.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-plan/SKILL.md +5 -6
- package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-summary/SKILL.md +35 -25
- package/assets/skills/oat-wave-execute/SKILL.md +11 -36
- package/assets/skills/oat-wave-program/SKILL.md +13 -37
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +9 -113
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +32 -184
- package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
- package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
- package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
- package/dist/config/oat-config.d.ts +0 -11
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +0 -34
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +0 -8
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +2 -10
- package/dist/validation/project-state.d.ts.map +1 -1
- package/dist/validation/project-state.js +42 -3
- package/package.json +2 -2
- package/assets/docs/contributing/explainer-kit-verification.md +0 -133
- package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
- package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
- package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
- package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
- package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
- package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
- package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
- package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
- package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
- package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
- package/assets/skills/explainer-kit/references/contracts.md +0 -324
- package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
- package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
- package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
- package/assets/skills/explainer-kit/references/visual-review.md +0 -71
- package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
- package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
- package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
- package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
- package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
- package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
- package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
- package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
- package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
- package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
- package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
- package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
- package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
- package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
- package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
- package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
- package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
- package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
- package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
- package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
- package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
- package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
- package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
- package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
- package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
- package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
- package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
- package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
- package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
- package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
- package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
- package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
- package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
- package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
- package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
- package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
- package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
- package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
- package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
- package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
|
@@ -1,531 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
-
import { join, relative, resolve, sep } from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
|
|
6
|
-
const MODES = new Set(['dedicated', 'completion-bookkeeping']);
|
|
7
|
-
const ARTIFACT_COMMIT_TOKEN = '$ARTIFACT_COMMIT';
|
|
8
|
-
const SHA_PATTERN = /^[a-f0-9]{40}$/;
|
|
9
|
-
const PACKAGE_COVERAGE_VERSION = 'explainer-kit.package-coverage/v2';
|
|
10
|
-
const TERMINAL_EVIDENCE_VERSION = 'explainer-kit.terminal-evidence/v1';
|
|
11
|
-
|
|
12
|
-
export async function planTrackedRunFinalization(request, context = {}) {
|
|
13
|
-
assertRequest(request);
|
|
14
|
-
const repoRoot = await realpathRequired(context.repoRoot, 'repoRoot');
|
|
15
|
-
const runRoot = await realpathRequired(request.runRoot, 'runRoot');
|
|
16
|
-
assertWithin(repoRoot, runRoot, 'runRoot');
|
|
17
|
-
|
|
18
|
-
const manifestPath = await realpath(request.manifestPath);
|
|
19
|
-
if (manifestPath !== resolve(runRoot, 'manifest.json')) {
|
|
20
|
-
throw new Error('manifestPath must identify manifest.json in runRoot.');
|
|
21
|
-
}
|
|
22
|
-
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
23
|
-
if (manifest.schemaVersion !== 'explainer-kit.manifest/v1') {
|
|
24
|
-
throw new Error(
|
|
25
|
-
'Finalization requires an explainer-kit.manifest/v1 record.',
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
if (['built-needs-review', 'failed'].includes(manifest.outcome)) {
|
|
29
|
-
const terminalEvidenceContract = await loadTerminalEvidenceContract(
|
|
30
|
-
context.coreRoot,
|
|
31
|
-
);
|
|
32
|
-
const { evidence: terminalEvidence } =
|
|
33
|
-
await terminalEvidenceContract.readTerminalEvidenceFile(runRoot, {
|
|
34
|
-
manifest,
|
|
35
|
-
});
|
|
36
|
-
return {
|
|
37
|
-
schemaVersion: 'oat-explainer-kit.finalization-plan/v1',
|
|
38
|
-
status: 'complete',
|
|
39
|
-
outcome: manifest.outcome,
|
|
40
|
-
commands: [],
|
|
41
|
-
push: null,
|
|
42
|
-
publicationAllowed: false,
|
|
43
|
-
evidenceDisposition: terminalEvidence.evidenceDisposition,
|
|
44
|
-
terminalEvidencePath: 'terminal-evidence.json',
|
|
45
|
-
...(terminalEvidence.supersededBy && {
|
|
46
|
-
supersededBy: terminalEvidence.supersededBy,
|
|
47
|
-
}),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const packageCoverage = await loadPackageCoverage(context.coreRoot);
|
|
51
|
-
|
|
52
|
-
const immutablePaths = (
|
|
53
|
-
await immutablePackagePaths(manifest, runRoot, packageCoverage)
|
|
54
|
-
).map((path) => toRepoPath(repoRoot, resolveRunPath(runRoot, path)));
|
|
55
|
-
const mutablePaths = [
|
|
56
|
-
toRepoPath(repoRoot, manifestPath),
|
|
57
|
-
toRepoPath(repoRoot, resolveRunPath(runRoot, manifest.buildRecord?.path)),
|
|
58
|
-
];
|
|
59
|
-
const artifactCommit =
|
|
60
|
-
request.commitMode === 'completion-bookkeeping'
|
|
61
|
-
? requiredSha(context.artifactCommit)
|
|
62
|
-
: context.artifactCommit
|
|
63
|
-
? requiredSha(context.artifactCommit)
|
|
64
|
-
: ARTIFACT_COMMIT_TOKEN;
|
|
65
|
-
const evidenceParent = context.currentHead
|
|
66
|
-
? requiredSha(context.currentHead)
|
|
67
|
-
: artifactCommit;
|
|
68
|
-
|
|
69
|
-
if (hasCommitEvidence(manifest, artifactCommit, immutablePaths)) {
|
|
70
|
-
return {
|
|
71
|
-
schemaVersion: 'oat-explainer-kit.finalization-plan/v1',
|
|
72
|
-
status: 'complete',
|
|
73
|
-
outcome: 'built-durable',
|
|
74
|
-
commands: [],
|
|
75
|
-
push: null,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const project = nonEmpty(context.project, 'project');
|
|
80
|
-
const recipe = nonEmpty(manifest.recipe?.id, 'manifest recipe id');
|
|
81
|
-
const artifact =
|
|
82
|
-
request.commitMode === 'dedicated' && !context.artifactCommit
|
|
83
|
-
? {
|
|
84
|
-
mode: 'create',
|
|
85
|
-
ref: ARTIFACT_COMMIT_TOKEN,
|
|
86
|
-
paths: immutablePaths,
|
|
87
|
-
commands: commitCommands(
|
|
88
|
-
immutablePaths,
|
|
89
|
-
`docs(oat): persist ${recipe} for ${project}`,
|
|
90
|
-
),
|
|
91
|
-
}
|
|
92
|
-
: {
|
|
93
|
-
mode: 'existing',
|
|
94
|
-
ref: artifactCommit,
|
|
95
|
-
paths: immutablePaths,
|
|
96
|
-
commands: [],
|
|
97
|
-
};
|
|
98
|
-
const evidenceCommit = {
|
|
99
|
-
parent: evidenceParent,
|
|
100
|
-
paths: mutablePaths,
|
|
101
|
-
commands: commitCommands(
|
|
102
|
-
mutablePaths,
|
|
103
|
-
`docs(oat): attest ${recipe} durability for ${project}`,
|
|
104
|
-
),
|
|
105
|
-
};
|
|
106
|
-
const attestation = {
|
|
107
|
-
coreCreatesCommits: false,
|
|
108
|
-
request: {
|
|
109
|
-
schemaVersion: 'explainer-kit.durability-evidence/v1',
|
|
110
|
-
manifestPath,
|
|
111
|
-
evidence: {
|
|
112
|
-
kind: 'commit',
|
|
113
|
-
repoRoot,
|
|
114
|
-
commit: artifactCommit,
|
|
115
|
-
paths: immutablePaths,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
const push = {
|
|
120
|
-
after: ['artifactCommit', 'evidenceCommit'],
|
|
121
|
-
commands: [{ command: 'git', args: ['push'] }],
|
|
122
|
-
instruction:
|
|
123
|
-
'Invoke one push only after the artifact and evidence commits exist; push both commits together.',
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
schemaVersion: 'oat-explainer-kit.finalization-plan/v1',
|
|
128
|
-
status: 'ready',
|
|
129
|
-
commitMode: request.commitMode,
|
|
130
|
-
repoRoot,
|
|
131
|
-
runRoot,
|
|
132
|
-
manifestPath,
|
|
133
|
-
relocatedFrom: request.relocatedFrom,
|
|
134
|
-
artifactCommit: artifact,
|
|
135
|
-
attestation,
|
|
136
|
-
evidenceCommit,
|
|
137
|
-
push,
|
|
138
|
-
commands: [
|
|
139
|
-
{
|
|
140
|
-
stage: 'artifact-commit',
|
|
141
|
-
commands: artifact.commands,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
stage: 'attestation',
|
|
145
|
-
action: 'call-core-recordDurability',
|
|
146
|
-
request: attestation.request,
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
stage: 'evidence-commit',
|
|
150
|
-
commands: evidenceCommit.commands,
|
|
151
|
-
},
|
|
152
|
-
{ stage: 'push', commands: push.commands },
|
|
153
|
-
],
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function verifyTrackedRunFinalization(plan, observation) {
|
|
158
|
-
if (plan?.status === 'complete') {
|
|
159
|
-
if (
|
|
160
|
-
!['built-durable', 'built-needs-review', 'failed'].includes(plan.outcome)
|
|
161
|
-
) {
|
|
162
|
-
return failed([
|
|
163
|
-
error(
|
|
164
|
-
'invalid-plan',
|
|
165
|
-
'Complete finalization plan has an unsupported outcome.',
|
|
166
|
-
),
|
|
167
|
-
]);
|
|
168
|
-
}
|
|
169
|
-
return {
|
|
170
|
-
ok: true,
|
|
171
|
-
outcome: plan.outcome,
|
|
172
|
-
pushAllowed: false,
|
|
173
|
-
reasons: [],
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const errors = [];
|
|
177
|
-
if (plan?.status !== 'ready') {
|
|
178
|
-
errors.push(error('invalid-plan', 'Finalization plan is not ready.'));
|
|
179
|
-
return failed(errors);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const artifact = observation?.artifactCommit;
|
|
183
|
-
if (!SHA_PATTERN.test(artifact?.sha ?? '')) {
|
|
184
|
-
errors.push(error('artifact-commit', 'Artifact commit SHA is missing.'));
|
|
185
|
-
}
|
|
186
|
-
if (
|
|
187
|
-
plan.artifactCommit.mode === 'existing' &&
|
|
188
|
-
artifact?.sha !== plan.artifactCommit.ref
|
|
189
|
-
) {
|
|
190
|
-
errors.push(
|
|
191
|
-
error('artifact-commit', 'Observed artifact commit does not match plan.'),
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
comparePaths({
|
|
195
|
-
expected: plan.artifactCommit.paths,
|
|
196
|
-
actual: artifact?.paths,
|
|
197
|
-
exact: plan.artifactCommit.mode === 'create',
|
|
198
|
-
label: 'artifact commit',
|
|
199
|
-
errors,
|
|
200
|
-
});
|
|
201
|
-
for (const path of plan.attestation.request.evidence.paths) {
|
|
202
|
-
if (isMutableRecord(path)) {
|
|
203
|
-
errors.push(
|
|
204
|
-
error(
|
|
205
|
-
'mutable-record',
|
|
206
|
-
`Commit evidence includes mutable record ${path}.`,
|
|
207
|
-
),
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const attestation = observation?.attestation;
|
|
213
|
-
const outcome = attestation?.outcome;
|
|
214
|
-
if (
|
|
215
|
-
!attestation ||
|
|
216
|
-
typeof attestation !== 'object' ||
|
|
217
|
-
typeof attestation.durable !== 'boolean' ||
|
|
218
|
-
!Array.isArray(attestation.errors) ||
|
|
219
|
-
!(
|
|
220
|
-
(attestation.durable === true && outcome === 'built-durable') ||
|
|
221
|
-
(attestation.durable === false && outcome === 'built-not-durable')
|
|
222
|
-
)
|
|
223
|
-
) {
|
|
224
|
-
errors.push(
|
|
225
|
-
error(
|
|
226
|
-
'attestation-outcome',
|
|
227
|
-
'Core attestation must include boolean durability, the corresponding exact terminal outcome, and an errors array.',
|
|
228
|
-
),
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const evidence = observation?.evidenceCommit;
|
|
233
|
-
if (!SHA_PATTERN.test(evidence?.sha ?? '')) {
|
|
234
|
-
errors.push(error('evidence-commit', 'Evidence commit SHA is missing.'));
|
|
235
|
-
}
|
|
236
|
-
const expectedEvidenceParent =
|
|
237
|
-
plan.evidenceCommit.parent === ARTIFACT_COMMIT_TOKEN
|
|
238
|
-
? artifact?.sha
|
|
239
|
-
: plan.evidenceCommit.parent;
|
|
240
|
-
if (evidence?.parent !== expectedEvidenceParent) {
|
|
241
|
-
errors.push(
|
|
242
|
-
error(
|
|
243
|
-
'commit-order',
|
|
244
|
-
'Evidence commit must immediately follow the artifact commit.',
|
|
245
|
-
),
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
comparePaths({
|
|
249
|
-
expected: plan.evidenceCommit.paths,
|
|
250
|
-
actual: evidence?.paths,
|
|
251
|
-
exact: true,
|
|
252
|
-
label: 'evidence commit',
|
|
253
|
-
errors,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
!sameSet(
|
|
258
|
-
observation?.unrelatedChangesBefore ?? [],
|
|
259
|
-
observation?.unrelatedChangesAfter ?? [],
|
|
260
|
-
)
|
|
261
|
-
) {
|
|
262
|
-
errors.push(
|
|
263
|
-
error(
|
|
264
|
-
'unrelated-change',
|
|
265
|
-
'Finalization changed or consumed an unrelated working-tree change.',
|
|
266
|
-
),
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return errors.length === 0
|
|
271
|
-
? { ok: true, outcome, pushAllowed: true, reasons: [] }
|
|
272
|
-
: failed(errors);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function commitCommands(paths, subject) {
|
|
276
|
-
return [
|
|
277
|
-
{ command: 'git', args: ['add', '--', ...paths] },
|
|
278
|
-
{
|
|
279
|
-
command: 'git',
|
|
280
|
-
args: ['commit', '--only', '-m', subject, '--', ...paths],
|
|
281
|
-
},
|
|
282
|
-
];
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function immutablePackagePaths(manifest, runRoot, packageCoverage) {
|
|
286
|
-
if (
|
|
287
|
-
!manifest.immutableHashes ||
|
|
288
|
-
typeof manifest.immutableHashes !== 'object' ||
|
|
289
|
-
Array.isArray(manifest.immutableHashes)
|
|
290
|
-
) {
|
|
291
|
-
throw new Error('Manifest does not identify immutable package hashes.');
|
|
292
|
-
}
|
|
293
|
-
const paths = Object.keys(manifest.immutableHashes);
|
|
294
|
-
if (paths.length === 0) {
|
|
295
|
-
throw new Error('Manifest does not identify a complete immutable package.');
|
|
296
|
-
}
|
|
297
|
-
const verifiedBytes = new Map();
|
|
298
|
-
for (const path of paths) {
|
|
299
|
-
const expected = manifest.immutableHashes[path];
|
|
300
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(expected)) {
|
|
301
|
-
throw new Error(`Manifest has an invalid immutable hash for ${path}.`);
|
|
302
|
-
}
|
|
303
|
-
const bytes = await readFile(resolveRunPath(runRoot, path));
|
|
304
|
-
const actual = `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
305
|
-
if (actual !== expected) {
|
|
306
|
-
throw new Error(`Immutable package hash mismatch for ${path}.`);
|
|
307
|
-
}
|
|
308
|
-
verifiedBytes.set(path, bytes);
|
|
309
|
-
}
|
|
310
|
-
const runMode =
|
|
311
|
-
manifest.recipe?.id === 'project-recap'
|
|
312
|
-
? verifiedRunMode(verifiedBytes.get('run-request.json'))
|
|
313
|
-
: undefined;
|
|
314
|
-
const required = packageCoverage.requiredImmutablePackagePaths(manifest, {
|
|
315
|
-
runMode,
|
|
316
|
-
});
|
|
317
|
-
const missing = required.filter(
|
|
318
|
-
(path) => !(path in manifest.immutableHashes),
|
|
319
|
-
);
|
|
320
|
-
if (missing.length > 0) {
|
|
321
|
-
throw new Error(
|
|
322
|
-
`Manifest immutable hashes do not cover the canonical package: ${missing.join(', ')}.`,
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
await packageCoverage.validateImmutablePackageEvidence(manifest, {
|
|
326
|
-
runMode,
|
|
327
|
-
read: (path) => verifiedBytes.get(path),
|
|
328
|
-
});
|
|
329
|
-
return paths;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function verifiedRunMode(bytes) {
|
|
333
|
-
if (!Buffer.isBuffer(bytes)) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
'Manifest must include hash-verified run-request.json before package coverage is evaluated.',
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
let request;
|
|
339
|
-
try {
|
|
340
|
-
request = JSON.parse(bytes.toString('utf8'));
|
|
341
|
-
} catch {
|
|
342
|
-
throw new Error(
|
|
343
|
-
'Hash-verified run-request.json must contain valid JSON before package coverage is evaluated.',
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
if (!['interactive', 'unattended'].includes(request?.mode)) {
|
|
347
|
-
throw new Error(
|
|
348
|
-
'Hash-verified run-request.json must declare interactive or unattended mode.',
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
return request.mode;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async function loadPackageCoverage(coreRoot) {
|
|
355
|
-
const root = await realpathRequired(coreRoot, 'coreRoot');
|
|
356
|
-
const modulePath = join(root, 'scripts', 'lib', 'package-coverage.mjs');
|
|
357
|
-
let loaded;
|
|
358
|
-
try {
|
|
359
|
-
loaded = await import(pathToFileURL(modulePath).href);
|
|
360
|
-
} catch {
|
|
361
|
-
throw new Error(
|
|
362
|
-
'Compatible explainer package coverage could not be loaded from coreRoot.',
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if (
|
|
366
|
-
loaded.PACKAGE_COVERAGE_VERSION !== PACKAGE_COVERAGE_VERSION ||
|
|
367
|
-
typeof loaded.requiredImmutablePackagePaths !== 'function' ||
|
|
368
|
-
typeof loaded.validateImmutablePackageEvidence !== 'function'
|
|
369
|
-
) {
|
|
370
|
-
throw new Error(
|
|
371
|
-
`coreRoot must provide ${PACKAGE_COVERAGE_VERSION} package coverage.`,
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
return loaded;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async function loadTerminalEvidenceContract(coreRoot) {
|
|
378
|
-
const root = await realpathRequired(coreRoot, 'coreRoot');
|
|
379
|
-
const modulePath = join(root, 'scripts', 'lib', 'terminal-evidence.mjs');
|
|
380
|
-
let loaded;
|
|
381
|
-
try {
|
|
382
|
-
loaded = await import(pathToFileURL(modulePath).href);
|
|
383
|
-
} catch {
|
|
384
|
-
throw new Error(
|
|
385
|
-
'Compatible terminal evidence could not be loaded from coreRoot.',
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
if (
|
|
389
|
-
loaded.TERMINAL_EVIDENCE_VERSION !== TERMINAL_EVIDENCE_VERSION ||
|
|
390
|
-
typeof loaded.assertTerminalEvidence !== 'function' ||
|
|
391
|
-
typeof loaded.readTerminalEvidenceFile !== 'function'
|
|
392
|
-
) {
|
|
393
|
-
throw new Error(
|
|
394
|
-
`coreRoot must provide ${TERMINAL_EVIDENCE_VERSION} confined validation.`,
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
return loaded;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function resolveRunPath(runRoot, path) {
|
|
401
|
-
const target = resolve(runRoot, path);
|
|
402
|
-
assertWithin(runRoot, target, `Manifest path ${path}`);
|
|
403
|
-
return target;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function hasCommitEvidence(manifest, commit, paths) {
|
|
407
|
-
if (manifest.outcome !== 'built-durable' || !SHA_PATTERN.test(commit)) {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
return (manifest.artifacts ?? [])
|
|
411
|
-
.filter(
|
|
412
|
-
({ status, rebuildable }) => status === 'built' && rebuildable !== true,
|
|
413
|
-
)
|
|
414
|
-
.every(({ durableEvidence = [] }) =>
|
|
415
|
-
durableEvidence.some(
|
|
416
|
-
(evidence) =>
|
|
417
|
-
evidence.kind === 'commit' &&
|
|
418
|
-
evidence.ref === commit &&
|
|
419
|
-
arraysEqual(evidence.paths, paths),
|
|
420
|
-
),
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function comparePaths({ expected, actual, exact, label, errors }) {
|
|
425
|
-
if (!Array.isArray(actual)) {
|
|
426
|
-
errors.push(error('commit-paths', `Observed ${label} paths are missing.`));
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
const missing = expected.filter((path) => !actual.includes(path));
|
|
430
|
-
const extra = exact ? actual.filter((path) => !expected.includes(path)) : [];
|
|
431
|
-
if (missing.length > 0 || extra.length > 0) {
|
|
432
|
-
errors.push(
|
|
433
|
-
error(
|
|
434
|
-
'unrelated-change',
|
|
435
|
-
`${label} paths differ from the plan (missing: ${missing.join(', ') || 'none'}; extra: ${extra.join(', ') || 'none'}).`,
|
|
436
|
-
),
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function assertRequest(request) {
|
|
442
|
-
if (!request || typeof request !== 'object') {
|
|
443
|
-
throw new TypeError('Finalization request is required.');
|
|
444
|
-
}
|
|
445
|
-
if (!MODES.has(request.commitMode)) {
|
|
446
|
-
throw new Error(
|
|
447
|
-
`Unsupported commitMode: ${request.commitMode ?? 'missing'}.`,
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
nonEmpty(request.runRoot, 'runRoot');
|
|
451
|
-
nonEmpty(request.manifestPath, 'manifestPath');
|
|
452
|
-
if (
|
|
453
|
-
request.relocatedFrom !== undefined &&
|
|
454
|
-
typeof request.relocatedFrom !== 'string'
|
|
455
|
-
) {
|
|
456
|
-
throw new TypeError('relocatedFrom must be a string when supplied.');
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function realpathRequired(value, label) {
|
|
461
|
-
return realpath(nonEmpty(value, label));
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function nonEmpty(value, label) {
|
|
465
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
466
|
-
throw new TypeError(`${label} is required.`);
|
|
467
|
-
}
|
|
468
|
-
return value;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function requiredSha(value) {
|
|
472
|
-
if (!SHA_PATTERN.test(value ?? '')) {
|
|
473
|
-
throw new TypeError(
|
|
474
|
-
'completion-bookkeeping requires a full artifactCommit SHA.',
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
return value;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function assertWithin(root, target, label) {
|
|
481
|
-
const path = relative(root, target);
|
|
482
|
-
if (path === '..' || path.startsWith(`..${sep}`)) {
|
|
483
|
-
throw new Error(`${label} escapes the repository or run root.`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function toRepoPath(repoRoot, absolutePath) {
|
|
488
|
-
assertWithin(repoRoot, absolutePath, 'Finalization path');
|
|
489
|
-
return relative(repoRoot, absolutePath).split(sep).join('/');
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function isMutableRecord(path) {
|
|
493
|
-
return (
|
|
494
|
-
path === 'manifest.json' ||
|
|
495
|
-
path === 'build-record.json' ||
|
|
496
|
-
path.endsWith('/manifest.json') ||
|
|
497
|
-
path.endsWith('/build-record.json')
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function arraysEqual(left, right) {
|
|
502
|
-
return (
|
|
503
|
-
left.length === right.length &&
|
|
504
|
-
left.every((value, index) => value === right[index])
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function sameSet(left, right) {
|
|
509
|
-
return (
|
|
510
|
-
left.length === right.length && left.every((value) => right.includes(value))
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function error(code, message) {
|
|
515
|
-
return { code, message };
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function failed(errors) {
|
|
519
|
-
return {
|
|
520
|
-
ok: false,
|
|
521
|
-
outcome: 'built-not-durable',
|
|
522
|
-
pushAllowed: false,
|
|
523
|
-
reasons: [
|
|
524
|
-
{
|
|
525
|
-
stage: 'finalization',
|
|
526
|
-
kind: 'pipeline-failure',
|
|
527
|
-
count: Math.min(Math.max(errors.length, 1), 50),
|
|
528
|
-
},
|
|
529
|
-
],
|
|
530
|
-
};
|
|
531
|
-
}
|