owlrunkit 0.15.0 → 0.16.0
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/CHANGELOG.md +100 -0
- package/README.md +274 -66
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
- package/package.json +2 -2
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/registry-release-policy-v1.json +8 -2
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +119 -0
- package/scripts/runkit-contract/core-contract.mjs +250 -115
- package/scripts/runkit-contract/core-successor.mjs +2043 -0
- package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
- package/scripts/runkit-contract/finalize.mjs +242 -21
- package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
- package/scripts/runkit-contract/formal-workflow.mjs +850 -0
- package/scripts/runkit-contract/human-status.mjs +553 -0
- package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
- package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
- package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
- package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
- package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
- package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
- package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
- package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
- package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
- package/scripts/runkit-contract/project-control-parser.mjs +830 -0
- package/scripts/runkit-contract/project-control-state.mjs +28 -0
- package/scripts/runkit-contract/provenance-common.mjs +19 -2
- package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
- package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
- package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
- package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
- package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
- package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
- package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
- package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
- package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
- package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
- package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
- package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
- package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
- package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
- package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
- package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
- package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
- package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
- package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
- package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
- package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
- package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
- package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
- package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
- package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
- package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
- package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
- package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
- package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
- package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
- package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
- package/scripts/runkit-contract/snapshot.mjs +1 -1
- package/scripts/runkit-contract/source-candidate.mjs +1382 -0
- package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
- package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
- package/scripts/runkit-contract/templates/assurance-request.json +13 -0
- package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
- package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
- package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
- package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
- package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
- package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
- package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
- package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
- package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
- package/scripts/runkit-contract/verification-receipt-gate.mjs +28 -0
|
@@ -20,11 +20,12 @@ import {
|
|
|
20
20
|
safeIdentifier,
|
|
21
21
|
sha256,
|
|
22
22
|
writeJsonAtomically,
|
|
23
|
-
|
|
23
|
+
writeJsonExclusiveAtomically,
|
|
24
24
|
} from "./provenance-common.mjs";
|
|
25
25
|
import { receiptSha256, validateReceiptLineage } from "./receipt-lineage.mjs";
|
|
26
26
|
import { verifySnapshotEvidence, verifySnapshotSourceBinding } from "./snapshot.mjs";
|
|
27
27
|
import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
|
|
28
|
+
import { verifySourceCandidateV2 } from "./source-candidate.mjs";
|
|
28
29
|
import {
|
|
29
30
|
validateVerificationContext,
|
|
30
31
|
verificationContextFingerprint,
|
|
@@ -36,6 +37,7 @@ const REQUEST_KEYS = [
|
|
|
36
37
|
"schemaVersion",
|
|
37
38
|
"receiptId",
|
|
38
39
|
"deliveryPacketPath",
|
|
40
|
+
"sourceCandidatePath",
|
|
39
41
|
"verificationContext",
|
|
40
42
|
"snapshotPaths",
|
|
41
43
|
"supersedesReceiptSha256",
|
|
@@ -56,6 +58,75 @@ const REPAIRABLE_INVALID_REASONS = new Set([
|
|
|
56
58
|
"source_mutated_during_verification",
|
|
57
59
|
]);
|
|
58
60
|
|
|
61
|
+
function canonicalJson(value) {
|
|
62
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
63
|
+
if (value && typeof value === "object") {
|
|
64
|
+
return `{${Object.keys(value).sort()
|
|
65
|
+
.map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
66
|
+
.join(",")}}`;
|
|
67
|
+
}
|
|
68
|
+
const encoded = JSON.stringify(value);
|
|
69
|
+
if (encoded === undefined) {
|
|
70
|
+
throw new Error("Finalize artifact is not canonical JSON.");
|
|
71
|
+
}
|
|
72
|
+
return encoded;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeFinalizeArtifactExact(filePath, value, label) {
|
|
76
|
+
if (!existsSync(filePath)) {
|
|
77
|
+
writeJsonExclusiveAtomically(filePath, value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const stat = lstatSync(filePath);
|
|
81
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
82
|
+
throw new Error(`Existing finalize artifact differs: ${label}.`);
|
|
83
|
+
}
|
|
84
|
+
const existing = readJson(realpathSync(filePath));
|
|
85
|
+
if (canonicalJson(existing) !== canonicalJson(value)) {
|
|
86
|
+
throw new Error(`Existing finalize artifact differs: ${label}.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeFinalizeLineageExact(
|
|
91
|
+
filePath,
|
|
92
|
+
{ expectedEntries, nextEntries },
|
|
93
|
+
) {
|
|
94
|
+
const validation = validateReceiptLineage(nextEntries);
|
|
95
|
+
if (!validation.valid || !validation.active) {
|
|
96
|
+
throw new Error("Replacement receipt lineage is invalid.");
|
|
97
|
+
}
|
|
98
|
+
if (!existsSync(filePath)) {
|
|
99
|
+
if (expectedEntries.length !== 0 || nextEntries.length !== 1) {
|
|
100
|
+
throw new Error("Receipt lineage changed before activation.");
|
|
101
|
+
}
|
|
102
|
+
writeJsonExclusiveAtomically(filePath, nextEntries);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const stat = lstatSync(filePath);
|
|
106
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
107
|
+
throw new Error("Receipt lineage changed before activation.");
|
|
108
|
+
}
|
|
109
|
+
const document = readJson(realpathSync(filePath));
|
|
110
|
+
const currentEntries = Array.isArray(document)
|
|
111
|
+
? document
|
|
112
|
+
: document?.receipts;
|
|
113
|
+
if (!Array.isArray(currentEntries)) {
|
|
114
|
+
throw new Error("Receipt lineage changed before activation.");
|
|
115
|
+
}
|
|
116
|
+
if (canonicalJson(currentEntries) === canonicalJson(nextEntries)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
canonicalJson(currentEntries) !== canonicalJson(expectedEntries)
|
|
121
|
+
|| nextEntries.length !== expectedEntries.length + 1
|
|
122
|
+
|| canonicalJson(nextEntries.slice(0, -1))
|
|
123
|
+
!== canonicalJson(expectedEntries)
|
|
124
|
+
) {
|
|
125
|
+
throw new Error("Receipt lineage changed before activation.");
|
|
126
|
+
}
|
|
127
|
+
writeJsonAtomically(filePath, nextEntries);
|
|
128
|
+
}
|
|
129
|
+
|
|
59
130
|
function changedPaths(packet) {
|
|
60
131
|
const files = packet?.changedFiles?.files ?? packet?.changedFiles?.wholeFileSha256;
|
|
61
132
|
if (!isRecord(files) || Object.keys(files).length === 0) {
|
|
@@ -64,14 +135,106 @@ function changedPaths(packet) {
|
|
|
64
135
|
return Object.keys(files).sort();
|
|
65
136
|
}
|
|
66
137
|
|
|
138
|
+
function candidateChangedPaths(candidate) {
|
|
139
|
+
return [...new Set(candidate.sourceManifest.entries.flatMap((entry) => (
|
|
140
|
+
entry.previousPath ? [entry.path, entry.previousPath] : [entry.path]
|
|
141
|
+
)))].sort();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function candidatePresentFiles(candidate) {
|
|
145
|
+
return Object.fromEntries(candidate.sourceManifest.entries
|
|
146
|
+
.filter((entry) => entry.operation !== "deleted")
|
|
147
|
+
.map((entry) => [entry.path, entry.sha256])
|
|
148
|
+
.sort(([left], [right]) => left.localeCompare(right)));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function loadFinalizeSource({ workspaceRoot, runId, request }) {
|
|
152
|
+
if (request.deliveryPacketPath !== undefined) {
|
|
153
|
+
const sourcePath = resolveExistingArtifact(
|
|
154
|
+
workspaceRoot,
|
|
155
|
+
request.deliveryPacketPath,
|
|
156
|
+
"deliveryPacketPath",
|
|
157
|
+
);
|
|
158
|
+
const source = readJson(sourcePath);
|
|
159
|
+
if (source.runId !== runId) {
|
|
160
|
+
throw new Error("Delivery packet runId does not match the execution.");
|
|
161
|
+
}
|
|
162
|
+
const sourceGate = verifyDeliveryPacket({ workspaceRoot, packet: source });
|
|
163
|
+
return {
|
|
164
|
+
kind: "delivery_packet_v1",
|
|
165
|
+
sourcePath,
|
|
166
|
+
source,
|
|
167
|
+
sourceGate,
|
|
168
|
+
changedPaths: changedPaths(source),
|
|
169
|
+
expectedFiles:
|
|
170
|
+
source.changedFiles?.files ?? source.changedFiles?.wholeFileSha256,
|
|
171
|
+
verifyCurrent: () => verifyDeliveryPacket({
|
|
172
|
+
workspaceRoot,
|
|
173
|
+
packet: source,
|
|
174
|
+
}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const sourcePath = resolveExistingArtifact(
|
|
179
|
+
workspaceRoot,
|
|
180
|
+
request.sourceCandidatePath,
|
|
181
|
+
"sourceCandidatePath",
|
|
182
|
+
);
|
|
183
|
+
const source = readJson(sourcePath);
|
|
184
|
+
if (source.runId !== runId) {
|
|
185
|
+
throw new Error("Source candidate runId does not match the execution.");
|
|
186
|
+
}
|
|
187
|
+
const candidateGate = verifySourceCandidateV2({
|
|
188
|
+
workspaceRoot,
|
|
189
|
+
candidatePath: request.sourceCandidatePath,
|
|
190
|
+
});
|
|
191
|
+
const fingerprint = source.sourceFingerprint?.sha256;
|
|
192
|
+
const sourceGate = {
|
|
193
|
+
status: candidateGate.status,
|
|
194
|
+
exitCode: candidateGate.exitCode,
|
|
195
|
+
declaredFingerprint: fingerprint,
|
|
196
|
+
recomputedFingerprint: candidateGate.status === "valid"
|
|
197
|
+
? candidateGate.sourceFingerprint
|
|
198
|
+
: fingerprint,
|
|
199
|
+
issues: candidateGate.issueCodes ?? [],
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
kind: "source_candidate_v2",
|
|
203
|
+
sourcePath,
|
|
204
|
+
source,
|
|
205
|
+
sourceGate,
|
|
206
|
+
changedPaths: candidateChangedPaths(source),
|
|
207
|
+
expectedFiles: candidatePresentFiles(source),
|
|
208
|
+
verifyCurrent: () => {
|
|
209
|
+
const gate = verifySourceCandidateV2({
|
|
210
|
+
workspaceRoot,
|
|
211
|
+
candidatePath: request.sourceCandidatePath,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
status: gate.status,
|
|
215
|
+
exitCode: gate.exitCode,
|
|
216
|
+
declaredFingerprint: fingerprint,
|
|
217
|
+
recomputedFingerprint: gate.status === "valid"
|
|
218
|
+
? gate.sourceFingerprint
|
|
219
|
+
: fingerprint,
|
|
220
|
+
issues: gate.issueCodes ?? [],
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
67
226
|
function validateRequest(request) {
|
|
68
227
|
assertAllowedKeys(request, "Finalize request", REQUEST_KEYS);
|
|
69
228
|
if (request.schemaVersion !== "OwlCodaRunKitFinalizeRequestV1") {
|
|
70
229
|
throw new Error("Unsupported finalize request schemaVersion.");
|
|
71
230
|
}
|
|
72
231
|
safeIdentifier(request.receiptId, "receiptId");
|
|
73
|
-
|
|
74
|
-
|
|
232
|
+
const hasDeliveryPacket = typeof request.deliveryPacketPath === "string"
|
|
233
|
+
&& request.deliveryPacketPath.length > 0;
|
|
234
|
+
const hasSourceCandidate = typeof request.sourceCandidatePath === "string"
|
|
235
|
+
&& request.sourceCandidatePath.length > 0;
|
|
236
|
+
if (hasDeliveryPacket === hasSourceCandidate) {
|
|
237
|
+
throw new Error("Finalize requires exactly one deliveryPacketPath or sourceCandidatePath.");
|
|
75
238
|
}
|
|
76
239
|
if (!Array.isArray(request.snapshotPaths)
|
|
77
240
|
|| request.snapshotPaths.some((value) => typeof value !== "string" || value.length === 0)) {
|
|
@@ -100,6 +263,9 @@ function validateRequest(request) {
|
|
|
100
263
|
if (request.supersedesReceiptSha256 === undefined && request.repairControl !== undefined) {
|
|
101
264
|
throw new Error("repairControl is only valid for repair finalize.");
|
|
102
265
|
}
|
|
266
|
+
if (hasSourceCandidate && request.supersedesReceiptSha256 !== undefined) {
|
|
267
|
+
throw new Error("SourceCandidate V2 finalize does not support receipt repair.");
|
|
268
|
+
}
|
|
103
269
|
if (request.repairControl !== undefined) validateRepairControl(request.repairControl);
|
|
104
270
|
const context = validateVerificationContext(request.verificationContext);
|
|
105
271
|
if (!context.valid) throw new Error(`Finalize verification context is invalid: ${context.issues.join("; ")}`);
|
|
@@ -280,6 +446,7 @@ function snapshotCommandReceipt({
|
|
|
280
446
|
expectedFiles,
|
|
281
447
|
expectedFingerprint,
|
|
282
448
|
partialSourceBinding = false,
|
|
449
|
+
sourceCandidate = null,
|
|
283
450
|
}) {
|
|
284
451
|
const absolutePath = resolveExistingArtifact(workspaceRoot, snapshotPath, "snapshotPaths entry");
|
|
285
452
|
const snapshot = readJson(absolutePath);
|
|
@@ -288,7 +455,29 @@ function snapshotCommandReceipt({
|
|
|
288
455
|
}
|
|
289
456
|
const validation = verifySnapshotEvidence({ workspaceRoot, runId, snapshot });
|
|
290
457
|
if (!validation.valid) throw new Error(validation.issues.join("; "));
|
|
291
|
-
if (
|
|
458
|
+
if (sourceCandidate !== null) {
|
|
459
|
+
const expected = JSON.stringify(sortedFileMap(expectedFiles));
|
|
460
|
+
const before = JSON.stringify(sortedFileMap(
|
|
461
|
+
snapshot.repositoryBefore?.selectedFiles ?? {},
|
|
462
|
+
));
|
|
463
|
+
const after = JSON.stringify(sortedFileMap(
|
|
464
|
+
snapshot.repositoryAfter?.selectedFiles ?? {},
|
|
465
|
+
));
|
|
466
|
+
const materialInputs = Object.entries(sortedFileMap(expectedFiles))
|
|
467
|
+
.map(([id, hash]) => ({ id, sha256: hash }));
|
|
468
|
+
const actualMaterialInputs = [
|
|
469
|
+
...(snapshot.command?.evidence?.materialInputs ?? []),
|
|
470
|
+
].sort((left, right) => String(left?.id).localeCompare(String(right?.id)));
|
|
471
|
+
if (
|
|
472
|
+
snapshot.repositoryBefore?.head !== sourceCandidate.baseline.head
|
|
473
|
+
|| snapshot.repositoryAfter?.head !== sourceCandidate.baseline.head
|
|
474
|
+
|| before !== expected
|
|
475
|
+
|| after !== expected
|
|
476
|
+
|| JSON.stringify(actualMaterialInputs) !== JSON.stringify(materialInputs)
|
|
477
|
+
) {
|
|
478
|
+
throw new Error("Snapshot source does not match SourceCandidate V2.");
|
|
479
|
+
}
|
|
480
|
+
} else if (!partialSourceBinding) {
|
|
292
481
|
const sourceBinding = verifySnapshotSourceBinding({ snapshot, expectedFiles, expectedFingerprint });
|
|
293
482
|
if (!sourceBinding.valid) throw new Error(sourceBinding.issues.join("; "));
|
|
294
483
|
}
|
|
@@ -323,6 +512,13 @@ function nextLineage({ lineagePath, receipt, supersedesReceiptSha256 }) {
|
|
|
323
512
|
}
|
|
324
513
|
return [{ receiptSha256: nextHash, receipt }];
|
|
325
514
|
}
|
|
515
|
+
if (
|
|
516
|
+
supersedesReceiptSha256 === undefined
|
|
517
|
+
&& existing.active.receiptSha256 === nextHash
|
|
518
|
+
&& canonicalJson(existing.active.receipt) === canonicalJson(receipt)
|
|
519
|
+
) {
|
|
520
|
+
return existing.entries;
|
|
521
|
+
}
|
|
326
522
|
if (supersedesReceiptSha256 !== undefined) {
|
|
327
523
|
if (existing.active.receiptSha256 !== supersedesReceiptSha256) {
|
|
328
524
|
throw new Error("Repair supersedesReceiptSha256 does not match the active receipt.");
|
|
@@ -603,14 +799,10 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
603
799
|
const { executionRoot, pinGate } = loadActiveExecution(workspaceRoot, runId);
|
|
604
800
|
if (pinGate.status !== "valid") return pinGate;
|
|
605
801
|
assertExecutionUnclosed(executionRoot, runId);
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
);
|
|
611
|
-
const packet = readJson(packetPath);
|
|
612
|
-
if (packet.runId !== runId) throw new Error("Delivery packet runId does not match the execution.");
|
|
613
|
-
const sourceGate = verifyDeliveryPacket({ workspaceRoot, packet });
|
|
802
|
+
const sourceBinding = loadFinalizeSource({ workspaceRoot, runId, request });
|
|
803
|
+
const packetPath = sourceBinding.sourcePath;
|
|
804
|
+
const packet = sourceBinding.source;
|
|
805
|
+
const sourceGate = sourceBinding.sourceGate;
|
|
614
806
|
if (sourceGate.status !== "valid") {
|
|
615
807
|
return {
|
|
616
808
|
status: sourceGate.status,
|
|
@@ -622,7 +814,7 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
622
814
|
}
|
|
623
815
|
const profilesDocument = readJson(path.join(workspaceRoot, ".owlcoda/runkit/profiles.json"));
|
|
624
816
|
const profileImpact = resolveProfileImpactProjection({
|
|
625
|
-
changedPaths: changedPaths
|
|
817
|
+
changedPaths: sourceBinding.changedPaths,
|
|
626
818
|
profiles: profilesDocument.profiles,
|
|
627
819
|
detailed: request.supersedesReceiptSha256 !== undefined,
|
|
628
820
|
});
|
|
@@ -636,7 +828,7 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
636
828
|
};
|
|
637
829
|
}
|
|
638
830
|
const contextFingerprint = verificationContextFingerprint(request.verificationContext);
|
|
639
|
-
const expectedFiles =
|
|
831
|
+
const expectedFiles = sourceBinding.expectedFiles;
|
|
640
832
|
const receiptsRoot = path.join(executionRoot, "verification-receipts");
|
|
641
833
|
const lineagePath = path.join(receiptsRoot, "receipt-lineage.json");
|
|
642
834
|
const existing = activeLineage(lineagePath);
|
|
@@ -679,6 +871,9 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
679
871
|
expectedFiles,
|
|
680
872
|
expectedFingerprint: sourceGate.recomputedFingerprint,
|
|
681
873
|
partialSourceBinding: request.supersedesReceiptSha256 !== undefined,
|
|
874
|
+
sourceCandidate: sourceBinding.kind === "source_candidate_v2"
|
|
875
|
+
? sourceBinding.source
|
|
876
|
+
: null,
|
|
682
877
|
}));
|
|
683
878
|
const commandReceipts = [...reusedCommandReceipts, ...replayedCommandReceipts];
|
|
684
879
|
const commandIds = commandReceipts.map(receipt => receipt.id);
|
|
@@ -714,6 +909,13 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
714
909
|
receiptId: request.receiptId,
|
|
715
910
|
status: "passed",
|
|
716
911
|
sourceFingerprint: sourceGate.recomputedFingerprint,
|
|
912
|
+
sourceArtifact: {
|
|
913
|
+
kind: sourceBinding.kind,
|
|
914
|
+
runId,
|
|
915
|
+
path: relativeToWorkspace(workspaceRoot, sourceBinding.sourcePath),
|
|
916
|
+
sha256: sha256(readFileSync(sourceBinding.sourcePath)),
|
|
917
|
+
sourceFingerprint: sourceGate.recomputedFingerprint,
|
|
918
|
+
},
|
|
717
919
|
verificationContextFingerprint: contextFingerprint,
|
|
718
920
|
goalContractSha256: currentGoal.goalContractSha256,
|
|
719
921
|
risk: structuredClone(currentGoal.risk),
|
|
@@ -758,7 +960,7 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
758
960
|
selectedProfileIds: profileImpact.profileIds,
|
|
759
961
|
activeParent: existing.active,
|
|
760
962
|
});
|
|
761
|
-
} else if (
|
|
963
|
+
} else if (sourceBinding.verifyCurrent().status !== "valid") {
|
|
762
964
|
throw new Error("Source changed before receipt persistence.");
|
|
763
965
|
}
|
|
764
966
|
};
|
|
@@ -768,14 +970,33 @@ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
|
|
|
768
970
|
const profileImpactPath = path.join(outputRoot, "profile-impact.json");
|
|
769
971
|
const gateInputPath = path.join(outputRoot, "verification-gate-input.json");
|
|
770
972
|
const gateOutputPath = path.join(outputRoot, "verification-gate-output.json");
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
973
|
+
writeFinalizeArtifactExact(
|
|
974
|
+
receiptPath,
|
|
975
|
+
receipt,
|
|
976
|
+
"verification receipt",
|
|
977
|
+
);
|
|
978
|
+
writeFinalizeArtifactExact(sourceGatePath, sourceGate, "source gate");
|
|
979
|
+
writeFinalizeArtifactExact(
|
|
980
|
+
profileImpactPath,
|
|
981
|
+
profileImpact,
|
|
982
|
+
"profile impact",
|
|
983
|
+
);
|
|
984
|
+
writeFinalizeArtifactExact(
|
|
985
|
+
gateInputPath,
|
|
986
|
+
gateInput,
|
|
987
|
+
"verification gate input",
|
|
988
|
+
);
|
|
989
|
+
writeFinalizeArtifactExact(
|
|
990
|
+
gateOutputPath,
|
|
991
|
+
gate,
|
|
992
|
+
"verification gate output",
|
|
993
|
+
);
|
|
776
994
|
if (typeof hooks.beforeLineageActivate === "function") hooks.beforeLineageActivate();
|
|
777
995
|
assertCurrentBindings();
|
|
778
|
-
|
|
996
|
+
writeFinalizeLineageExact(lineagePath, {
|
|
997
|
+
expectedEntries: existing.entries,
|
|
998
|
+
nextEntries: lineage,
|
|
999
|
+
});
|
|
779
1000
|
return {
|
|
780
1001
|
status: gate.decision,
|
|
781
1002
|
exitCode: 0,
|