owlrunkit 0.13.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +674 -0
  3. package/NOTICE.md +137 -0
  4. package/README.md +44 -0
  5. package/SOURCE.md +10 -0
  6. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_1.md +160 -0
  7. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +459 -0
  8. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +408 -0
  9. package/integrations/codex/skills/owlcoda-runkit/agents/openai.yaml +4 -0
  10. package/integrations/codex/skills/owlcoda-runkit/assets/templates/agents-snippet.md +7 -0
  11. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +10 -0
  12. package/integrations/codex/skills/owlcoda-runkit/assets/templates/coverage-adopt-request.json +26 -0
  13. package/integrations/codex/skills/owlcoda-runkit/assets/templates/execution-delivery-packet.json +13 -0
  14. package/integrations/codex/skills/owlcoda-runkit/assets/templates/finalize-request.json +18 -0
  15. package/integrations/codex/skills/owlcoda-runkit/assets/templates/goal-contract.json +11 -0
  16. package/integrations/codex/skills/owlcoda-runkit/assets/templates/profiles.json +9 -0
  17. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +11 -0
  18. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resource-preflight-request.json +56 -0
  19. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resume-request.json +10 -0
  20. package/integrations/codex/skills/owlcoda-runkit/assets/templates/snapshot-request.json +23 -0
  21. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-context.json +15 -0
  22. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-receipt.json +11 -0
  23. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verify-plan-request.json +31 -0
  24. package/integrations/codex/skills/owlcoda-runkit/assets/templates/visual-smoke-request.json +32 -0
  25. package/integrations/codex/skills/owlcoda-runkit/assets/templates/worker-lease.json +7 -0
  26. package/package.json +24 -0
  27. package/packages/attest/LICENSE +674 -0
  28. package/packages/attest/README.md +63 -0
  29. package/packages/attest/cli/owlcoda-attest.mjs +155 -0
  30. package/packages/attest/consumer-surface-v1.json +255 -0
  31. package/packages/attest/distribution-manifest-v1.json +104 -0
  32. package/packages/attest/fixtures/bad/accepted-authority-leak.json +7 -0
  33. package/packages/attest/fixtures/bad/accepted-failed-receipt.json +7 -0
  34. package/packages/attest/fixtures/bad/active-context-binding-mismatch.json +7 -0
  35. package/packages/attest/fixtures/bad/active-source-binding-mismatch.json +7 -0
  36. package/packages/attest/fixtures/bad/context-fingerprint-mismatch.json +7 -0
  37. package/packages/attest/fixtures/bad/duplicate-object-key.json +8 -0
  38. package/packages/attest/fixtures/bad/multiple-active-leaves.json +10 -0
  39. package/packages/attest/fixtures/bad/orphan-replacement.json +7 -0
  40. package/packages/attest/fixtures/bad/root-shape-extra-property.json +8 -0
  41. package/packages/attest/fixtures/bad/source-fingerprint-mismatch.json +7 -0
  42. package/packages/attest/fixtures/good/accepted-replacement-chain.json +59 -0
  43. package/packages/attest/fixtures/good/blocked-single-leaf.json +9 -0
  44. package/packages/attest/fixtures/index.json +90 -0
  45. package/packages/attest/sbom.spdx.json +33 -0
  46. package/packages/attest/schemas/attestation-ref-v1.schema.json +44 -0
  47. package/packages/attest/schemas/attestation-result-v1.schema.json +163 -0
  48. package/packages/attest/schemas/offline-attestation-bundle-v1.schema.json +30 -0
  49. package/packages/attest/schemas/public-verification-bundle-v1.schema.json +74 -0
  50. package/packages/attest/schemas/quick-verification-receipt-v1.schema.json +219 -0
  51. package/packages/attest/schemas/workspace-snapshot-v1.schema.json +135 -0
  52. package/packages/attest/src/canonical.mjs +12 -0
  53. package/packages/attest/src/formal.mjs +762 -0
  54. package/packages/attest/src/index.mjs +113 -0
  55. package/packages/attest/src/offline-bundle.mjs +108 -0
  56. package/packages/attest/src/quick-receipt-contract.mjs +156 -0
  57. package/packages/attest/src/quick.mjs +274 -0
  58. package/packages/attest/src/reference-contract.mjs +64 -0
  59. package/packages/attest/src/reference.mjs +131 -0
  60. package/packages/attest/src/workspace-snapshot.mjs +171 -0
  61. package/registry-release-policy-v1.json +34 -0
  62. package/sbom.spdx.json +39 -0
  63. package/scripts/runkit-contract/acceptance-evidence.mjs +103 -0
  64. package/scripts/runkit-contract/core-contract.mjs +392 -0
  65. package/scripts/runkit-contract/coverage-adoption.mjs +414 -0
  66. package/scripts/runkit-contract/delivery-create.mjs +252 -0
  67. package/scripts/runkit-contract/delivery-selection.mjs +31 -0
  68. package/scripts/runkit-contract/finalize.mjs +790 -0
  69. package/scripts/runkit-contract/inspect-presentation.mjs +183 -0
  70. package/scripts/runkit-contract/install-codex-skill.mjs +239 -0
  71. package/scripts/runkit-contract/lease-lifecycle.mjs +324 -0
  72. package/scripts/runkit-contract/lifecycle-orchestration.mjs +415 -0
  73. package/scripts/runkit-contract/offline-store.mjs +248 -0
  74. package/scripts/runkit-contract/profile-impact.mjs +223 -0
  75. package/scripts/runkit-contract/provenance-common.mjs +153 -0
  76. package/scripts/runkit-contract/quick-attest.mjs +4 -0
  77. package/scripts/runkit-contract/quick-canonical.mjs +41 -0
  78. package/scripts/runkit-contract/quick-metrics.mjs +86 -0
  79. package/scripts/runkit-contract/quick-receipt.mjs +139 -0
  80. package/scripts/runkit-contract/quick-verify.mjs +201 -0
  81. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +3 -0
  82. package/scripts/runkit-contract/ready-for-commit.mjs +188 -0
  83. package/scripts/runkit-contract/receipt-lineage.mjs +332 -0
  84. package/scripts/runkit-contract/registry-adoption-gate.mjs +117 -0
  85. package/scripts/runkit-contract/repair-execution.mjs +997 -0
  86. package/scripts/runkit-contract/resource-preflight.mjs +829 -0
  87. package/scripts/runkit-contract/resume-execution.mjs +569 -0
  88. package/scripts/runkit-contract/runkit-cli.mjs +1214 -0
  89. package/scripts/runkit-contract/schemas/closeout-receipt-v2.schema.json +42 -0
  90. package/scripts/runkit-contract/schemas/core-artifact-v2.schema.json +23 -0
  91. package/scripts/runkit-contract/schemas/core-artifact.schema.json +23 -0
  92. package/scripts/runkit-contract/schemas/coverage-adopt-request-v1.schema.json +66 -0
  93. package/scripts/runkit-contract/schemas/engine-pin-v2.schema.json +14 -0
  94. package/scripts/runkit-contract/schemas/engine-pin.schema.json +14 -0
  95. package/scripts/runkit-contract/schemas/evidence-coverage-index-v1.schema.json +131 -0
  96. package/scripts/runkit-contract/schemas/finalize-request-v1.schema.json +131 -0
  97. package/scripts/runkit-contract/schemas/profiles.schema.json +47 -0
  98. package/scripts/runkit-contract/schemas/project-config-v2.schema.json +13 -0
  99. package/scripts/runkit-contract/schemas/project-config.schema.json +13 -0
  100. package/scripts/runkit-contract/schemas/ready-for-commit-v1.schema.json +26 -0
  101. package/scripts/runkit-contract/schemas/replayable-evidence-v1.schema.json +82 -0
  102. package/scripts/runkit-contract/schemas/resource-preflight-request-v1.schema.json +178 -0
  103. package/scripts/runkit-contract/schemas/resource-preflight-v1.schema.json +153 -0
  104. package/scripts/runkit-contract/schemas/resume-attempt-v1.schema.json +80 -0
  105. package/scripts/runkit-contract/schemas/resume-request-v1.schema.json +77 -0
  106. package/scripts/runkit-contract/schemas/snapshot-v1.schema.json +71 -0
  107. package/scripts/runkit-contract/schemas/verification-context-v1.schema.json +92 -0
  108. package/scripts/runkit-contract/schemas/verification-plan-v1.schema.json +150 -0
  109. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +118 -0
  110. package/scripts/runkit-contract/schemas/verify-plan-request-v1.schema.json +56 -0
  111. package/scripts/runkit-contract/schemas/visual-smoke-request-v1.schema.json +64 -0
  112. package/scripts/runkit-contract/schemas/visual-smoke-result-v1.schema.json +68 -0
  113. package/scripts/runkit-contract/snapshot.mjs +340 -0
  114. package/scripts/runkit-contract/source-fingerprint.mjs +404 -0
  115. package/scripts/runkit-contract/templates/coverage-adopt-request.json +26 -0
  116. package/scripts/runkit-contract/templates/finalize-request.json +18 -0
  117. package/scripts/runkit-contract/templates/goal-contract.json +11 -0
  118. package/scripts/runkit-contract/templates/ready-for-commit-request.json +11 -0
  119. package/scripts/runkit-contract/templates/resource-preflight-request.json +56 -0
  120. package/scripts/runkit-contract/templates/resume-request.json +10 -0
  121. package/scripts/runkit-contract/templates/snapshot-request.json +23 -0
  122. package/scripts/runkit-contract/templates/verify-plan-request.json +31 -0
  123. package/scripts/runkit-contract/templates/visual-smoke-request.json +32 -0
  124. package/scripts/runkit-contract/templates/worker-lease.json +7 -0
  125. package/scripts/runkit-contract/verification-context.mjs +141 -0
  126. package/scripts/runkit-contract/verification-plan.mjs +608 -0
  127. package/scripts/runkit-contract/verification-receipt-gate.mjs +566 -0
  128. package/scripts/runkit-contract/visual-smoke.mjs +410 -0
@@ -0,0 +1,790 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ readFileSync,
5
+ realpathSync,
6
+ } from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import { validateReplayableEvidence } from "./acceptance-evidence.mjs";
10
+ import {
11
+ resolveProfileImpactProjection,
12
+ } from "./profile-impact.mjs";
13
+ import {
14
+ assertAllowedKeys,
15
+ isRecord,
16
+ loadActiveExecution,
17
+ readJson,
18
+ relativeToWorkspace,
19
+ resolveExistingArtifact,
20
+ safeIdentifier,
21
+ sha256,
22
+ writeJsonAtomically,
23
+ writeJsonExclusive,
24
+ } from "./provenance-common.mjs";
25
+ import { receiptSha256, validateReceiptLineage } from "./receipt-lineage.mjs";
26
+ import { verifySnapshotEvidence, verifySnapshotSourceBinding } from "./snapshot.mjs";
27
+ import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
28
+ import {
29
+ validateVerificationContext,
30
+ verificationContextFingerprint,
31
+ } from "./verification-context.mjs";
32
+ import { validateVerificationReceiptGate } from "./verification-receipt-gate.mjs";
33
+ import { assertExecutionUnclosed } from "./lease-lifecycle.mjs";
34
+
35
+ const REQUEST_KEYS = [
36
+ "schemaVersion",
37
+ "receiptId",
38
+ "deliveryPacketPath",
39
+ "verificationContext",
40
+ "snapshotPaths",
41
+ "supersedesReceiptSha256",
42
+ "reusedCommandReceipts",
43
+ "repairControl",
44
+ ];
45
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
46
+ const RISK_MODES = new Set(["lightweight", "standard", "full"]);
47
+ const RISK_CATEGORIES = new Set([
48
+ "backtest",
49
+ "funds",
50
+ "migration",
51
+ "production",
52
+ "release",
53
+ ]);
54
+ const REPAIRABLE_INVALID_REASONS = new Set([
55
+ "receipt_source_mismatch",
56
+ "source_mutated_during_verification",
57
+ ]);
58
+
59
+ function changedPaths(packet) {
60
+ const files = packet?.changedFiles?.files ?? packet?.changedFiles?.wholeFileSha256;
61
+ if (!isRecord(files) || Object.keys(files).length === 0) {
62
+ throw new Error("Delivery packet does not declare changed files.");
63
+ }
64
+ return Object.keys(files).sort();
65
+ }
66
+
67
+ function validateRequest(request) {
68
+ assertAllowedKeys(request, "Finalize request", REQUEST_KEYS);
69
+ if (request.schemaVersion !== "OwlCodaRunKitFinalizeRequestV1") {
70
+ throw new Error("Unsupported finalize request schemaVersion.");
71
+ }
72
+ safeIdentifier(request.receiptId, "receiptId");
73
+ if (typeof request.deliveryPacketPath !== "string" || request.deliveryPacketPath.length === 0) {
74
+ throw new Error("Finalize request requires deliveryPacketPath.");
75
+ }
76
+ if (!Array.isArray(request.snapshotPaths)
77
+ || request.snapshotPaths.some((value) => typeof value !== "string" || value.length === 0)) {
78
+ throw new Error("Finalize request requires snapshotPaths.");
79
+ }
80
+ if (request.supersedesReceiptSha256 !== undefined
81
+ && (typeof request.supersedesReceiptSha256 !== "string"
82
+ || !SHA256_PATTERN.test(request.supersedesReceiptSha256))) {
83
+ throw new Error("Finalize supersedesReceiptSha256 must be a lowercase SHA-256.");
84
+ }
85
+ if (request.reusedCommandReceipts !== undefined
86
+ && !Array.isArray(request.reusedCommandReceipts)) {
87
+ throw new Error("Finalize reusedCommandReceipts must be an array.");
88
+ }
89
+ if (request.snapshotPaths.length === 0
90
+ && (request.reusedCommandReceipts?.length ?? 0) === 0) {
91
+ throw new Error("Finalize requires snapshotPaths or reusedCommandReceipts.");
92
+ }
93
+ if (request.reusedCommandReceipts !== undefined
94
+ && request.supersedesReceiptSha256 === undefined) {
95
+ throw new Error("Reused command receipts require supersedesReceiptSha256.");
96
+ }
97
+ if (request.supersedesReceiptSha256 !== undefined && request.repairControl === undefined) {
98
+ throw new Error("Repair finalize requires repairControl.");
99
+ }
100
+ if (request.supersedesReceiptSha256 === undefined && request.repairControl !== undefined) {
101
+ throw new Error("repairControl is only valid for repair finalize.");
102
+ }
103
+ if (request.repairControl !== undefined) validateRepairControl(request.repairControl);
104
+ const context = validateVerificationContext(request.verificationContext);
105
+ if (!context.valid) throw new Error(`Finalize verification context is invalid: ${context.issues.join("; ")}`);
106
+ }
107
+
108
+ function validateRepairControl(value) {
109
+ assertAllowedKeys(value, "Finalize repairControl", [
110
+ "repairPlanPath",
111
+ "repairPlanSha256",
112
+ "parentBindingMode",
113
+ "goalContractSha256",
114
+ "risk",
115
+ "profilesSha256",
116
+ "executableBindings",
117
+ ]);
118
+ if (typeof value.repairPlanPath !== "string" || value.repairPlanPath.length === 0) {
119
+ throw new Error("Finalize repairControl requires repairPlanPath.");
120
+ }
121
+ if (value.parentBindingMode !== "receipt") {
122
+ throw new Error("Finalize repairControl parentBindingMode is invalid.");
123
+ }
124
+ for (const field of ["repairPlanSha256", "goalContractSha256", "profilesSha256"]) {
125
+ if (typeof value[field] !== "string" || !SHA256_PATTERN.test(value[field])) {
126
+ throw new Error(`Finalize repairControl ${field} must be a lowercase SHA-256.`);
127
+ }
128
+ }
129
+ validateRisk(value.risk, "Finalize repairControl risk");
130
+ if (!Array.isArray(value.executableBindings) || value.executableBindings.length === 0) {
131
+ throw new Error("Finalize repairControl requires executableBindings.");
132
+ }
133
+ const commandIds = [];
134
+ for (const binding of value.executableBindings) {
135
+ assertAllowedKeys(binding, "Finalize executable binding", [
136
+ "commandId",
137
+ "executable",
138
+ "sha256",
139
+ ]);
140
+ safeIdentifier(binding.commandId, "repair executable commandId");
141
+ if (typeof binding.executable !== "string" || !path.isAbsolute(binding.executable)) {
142
+ throw new Error("Finalize repair executable must be an absolute path.");
143
+ }
144
+ if (typeof binding.sha256 !== "string" || !SHA256_PATTERN.test(binding.sha256)) {
145
+ throw new Error("Finalize repair executable sha256 must be a lowercase SHA-256.");
146
+ }
147
+ commandIds.push(binding.commandId);
148
+ }
149
+ if (new Set(commandIds).size !== commandIds.length) {
150
+ throw new Error("Finalize repair executable commandIds must be unique.");
151
+ }
152
+ }
153
+
154
+ function validateRisk(value, label) {
155
+ if (!isRecord(value)
156
+ || !RISK_MODES.has(value.riskMode)
157
+ || !Array.isArray(value.riskCategories)
158
+ || value.riskCategories.some(category => !RISK_CATEGORIES.has(category))
159
+ || new Set(value.riskCategories).size !== value.riskCategories.length
160
+ || value.riskCategories.some((category, index) =>
161
+ index > 0 && value.riskCategories[index - 1] >= category)) {
162
+ throw new Error(`${label} must contain a valid riskMode and sorted unique riskCategories.`);
163
+ }
164
+ return {
165
+ riskMode: value.riskMode,
166
+ riskCategories: [...value.riskCategories],
167
+ };
168
+ }
169
+
170
+ function goalBinding(executionRoot) {
171
+ const goalPath = assertRegularFile(
172
+ path.join(executionRoot, "goal-contract.json"),
173
+ "Repair goal contract",
174
+ );
175
+ const goalBytes = readFileSync(goalPath);
176
+ const goal = JSON.parse(goalBytes.toString("utf8"));
177
+ const executionPlan = readJson(assertRegularFile(
178
+ path.join(executionRoot, "execution-plan.json"),
179
+ "Repair execution plan",
180
+ ));
181
+ const risk = validateRisk({
182
+ riskMode: goal.riskMode ?? "standard",
183
+ riskCategories: [...new Set(goal.riskCategories ?? [])].sort(),
184
+ }, "Goal contract risk");
185
+ return {
186
+ goalContractSha256: sha256(goalBytes),
187
+ risk,
188
+ executionPlanGoalBindingValid: executionPlan.goalContractSha256 === sha256(goalBytes),
189
+ };
190
+ }
191
+
192
+ function sameRisk(left, right) {
193
+ return left?.riskMode === right?.riskMode
194
+ && JSON.stringify(left?.riskCategories) === JSON.stringify(right?.riskCategories);
195
+ }
196
+
197
+ function assertRegularFile(filePath, label) {
198
+ const stat = lstatSync(filePath);
199
+ if (stat.isSymbolicLink() || !stat.isFile()) {
200
+ throw new Error(`${label} must be a regular file, not a symlink.`);
201
+ }
202
+ return realpathSync(filePath);
203
+ }
204
+
205
+ function assertRepairControlCurrent({
206
+ workspaceRoot,
207
+ executionRoot,
208
+ packetPath,
209
+ packet,
210
+ repairControl,
211
+ request,
212
+ profilesDocument,
213
+ selectedProfileIds,
214
+ activeParent,
215
+ }) {
216
+ const sourceGate = verifyDeliveryPacket({ workspaceRoot, packet });
217
+ if (sourceGate.status !== "valid") {
218
+ throw new Error("Repair source changed before receipt persistence.");
219
+ }
220
+ const planPath = resolveExistingArtifact(
221
+ workspaceRoot,
222
+ repairControl.repairPlanPath,
223
+ "repairControl.repairPlanPath",
224
+ );
225
+ const planBytes = readFileSync(planPath);
226
+ if (sha256(planBytes) !== repairControl.repairPlanSha256) {
227
+ throw new Error("Repair plan changed before receipt persistence.");
228
+ }
229
+ const plan = JSON.parse(planBytes.toString("utf8"));
230
+ const currentGoal = goalBinding(executionRoot);
231
+ if (currentGoal.goalContractSha256 !== repairControl.goalContractSha256) {
232
+ throw new Error("Repair goal contract changed before receipt persistence.");
233
+ }
234
+ if (!sameRisk(currentGoal.risk, repairControl.risk)) {
235
+ throw new Error("Repair risk binding changed before receipt persistence.");
236
+ }
237
+ const parentBindingValid = SHA256_PATTERN.test(activeParent?.receipt?.goalContractSha256 ?? "")
238
+ && activeParent.receipt.goalContractSha256 === currentGoal.goalContractSha256
239
+ && sameRisk(validateRisk(activeParent.receipt.risk, "Active parent receipt risk"), currentGoal.risk);
240
+ if (!parentBindingValid) {
241
+ throw new Error("Repair cannot cross parent receipt goal or risk drift.");
242
+ }
243
+ const profilesPath = assertRegularFile(
244
+ path.join(workspaceRoot, ".owlcoda/runkit/profiles.json"),
245
+ "Repair profiles",
246
+ );
247
+ if (sha256(readFileSync(profilesPath)) !== repairControl.profilesSha256) {
248
+ throw new Error("Repair profiles changed before receipt persistence.");
249
+ }
250
+ for (const binding of repairControl.executableBindings) {
251
+ const executable = assertRegularFile(
252
+ binding.executable,
253
+ `Repair executable ${binding.commandId}`,
254
+ );
255
+ if (executable !== binding.executable
256
+ || sha256(readFileSync(executable)) !== binding.sha256) {
257
+ throw new Error(`Repair executable changed before receipt persistence: ${binding.commandId}`);
258
+ }
259
+ }
260
+ assertRepairPlanSemantics({
261
+ workspaceRoot,
262
+ packetPath,
263
+ packet,
264
+ plan,
265
+ request,
266
+ repairControl,
267
+ profilesDocument,
268
+ selectedProfileIds,
269
+ activeParent,
270
+ currentGoal,
271
+ });
272
+ return { sourceGate, plan, currentGoal };
273
+ }
274
+
275
+ function snapshotCommandReceipt({
276
+ workspaceRoot,
277
+ runId,
278
+ snapshotPath,
279
+ contextFingerprint,
280
+ expectedFiles,
281
+ expectedFingerprint,
282
+ partialSourceBinding = false,
283
+ }) {
284
+ const absolutePath = resolveExistingArtifact(workspaceRoot, snapshotPath, "snapshotPaths entry");
285
+ const snapshot = readJson(absolutePath);
286
+ if (snapshot.verificationContextFingerprint !== contextFingerprint) {
287
+ throw new Error("Snapshot verification context does not match the finalize request.");
288
+ }
289
+ const validation = verifySnapshotEvidence({ workspaceRoot, runId, snapshot });
290
+ if (!validation.valid) throw new Error(validation.issues.join("; "));
291
+ if (!partialSourceBinding) {
292
+ const sourceBinding = verifySnapshotSourceBinding({ snapshot, expectedFiles, expectedFingerprint });
293
+ if (!sourceBinding.valid) throw new Error(sourceBinding.issues.join("; "));
294
+ }
295
+ const evidenceValidation = validateReplayableEvidence(snapshot.command.evidence);
296
+ if (!evidenceValidation.valid) throw new Error(evidenceValidation.issues.join("; "));
297
+ return {
298
+ id: snapshot.snapshotId,
299
+ evidence: snapshot.command.evidence,
300
+ exitCode: snapshot.command.exitCode,
301
+ stdoutSha256: snapshot.command.stdoutSha256,
302
+ stderrSha256: snapshot.command.stderrSha256,
303
+ };
304
+ }
305
+
306
+ function activeLineage(lineagePath) {
307
+ if (!existsSync(lineagePath)) return { entries: [], active: null };
308
+ const existing = readJson(lineagePath);
309
+ const entries = Array.isArray(existing) ? existing : existing.receipts;
310
+ const validation = validateReceiptLineage(entries);
311
+ if (!validation.valid || !validation.active) {
312
+ throw new Error("Existing receipt lineage is invalid.");
313
+ }
314
+ return { entries, active: validation.active };
315
+ }
316
+
317
+ function nextLineage({ lineagePath, receipt, supersedesReceiptSha256 }) {
318
+ const nextHash = receiptSha256(receipt);
319
+ const existing = activeLineage(lineagePath);
320
+ if (existing.active === null) {
321
+ if (supersedesReceiptSha256 !== undefined) {
322
+ throw new Error("A repair supersedes receipt requires an existing active receipt.");
323
+ }
324
+ return [{ receiptSha256: nextHash, receipt }];
325
+ }
326
+ if (supersedesReceiptSha256 !== undefined) {
327
+ if (existing.active.receiptSha256 !== supersedesReceiptSha256) {
328
+ throw new Error("Repair supersedesReceiptSha256 does not match the active receipt.");
329
+ }
330
+ } else if (existing.active.receipt.status !== "invalidated_by_concurrent_write") {
331
+ throw new Error("A new receipt may only replace an invalidated active receipt.");
332
+ }
333
+ return [
334
+ ...existing.entries,
335
+ {
336
+ receiptSha256: nextHash,
337
+ parentReceiptSha256: existing.active.receiptSha256,
338
+ receipt,
339
+ },
340
+ ];
341
+ }
342
+
343
+ function outputArtifactsValid(workspaceRoot, commandReceipt) {
344
+ const evidenceValidation = validateReplayableEvidence(commandReceipt?.evidence);
345
+ if (!evidenceValidation.valid) throw new Error(evidenceValidation.issues.join("; "));
346
+ if (commandReceipt.exitCode !== 0) throw new Error("Reused command receipt did not pass.");
347
+ const outputByPath = new Map();
348
+ for (const artifact of commandReceipt.evidence.outputArtifacts ?? []) {
349
+ const absolutePath = resolveExistingArtifact(workspaceRoot, artifact.path, "reused output artifact");
350
+ const actual = sha256(readFileSync(absolutePath));
351
+ if (actual !== artifact.sha256) {
352
+ throw new Error(`Reused output artifact hash mismatch: ${artifact.path}`);
353
+ }
354
+ outputByPath.set(artifact.path, artifact.sha256);
355
+ }
356
+ const outputHashes = [...outputByPath.values()];
357
+ if (!outputHashes.includes(commandReceipt.stdoutSha256)
358
+ || !outputHashes.includes(commandReceipt.stderrSha256)) {
359
+ throw new Error("Reused command receipt stdout/stderr hashes are not bound output artifacts.");
360
+ }
361
+ }
362
+
363
+ function commandMaterialMap({ workspaceRoot, commandReceipt, expectedFiles }) {
364
+ outputArtifactsValid(workspaceRoot, commandReceipt);
365
+ const materials = {};
366
+ for (const material of commandReceipt.evidence.materialInputs ?? []) {
367
+ const absolutePath = resolveExistingArtifact(workspaceRoot, material.id, "command material input");
368
+ const actual = sha256(readFileSync(absolutePath));
369
+ if (actual !== material.sha256) {
370
+ throw new Error(`Command material input hash mismatch: ${material.id}`);
371
+ }
372
+ if (expectedFiles[material.id] !== undefined) {
373
+ if (expectedFiles[material.id] !== material.sha256) {
374
+ throw new Error(`Command material input does not match the current DeliveryPacket: ${material.id}`);
375
+ }
376
+ materials[material.id] = material.sha256;
377
+ }
378
+ }
379
+ return materials;
380
+ }
381
+
382
+ function sortedFileMap(value) {
383
+ return Object.fromEntries(
384
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)),
385
+ );
386
+ }
387
+
388
+ function exactCommandMatches(profileCommand, commandReceipt) {
389
+ const evidence = commandReceipt.evidence;
390
+ const executable = evidence?.launcher?.executable;
391
+ const exactArgv = evidence?.argv;
392
+ if (typeof executable !== "string"
393
+ || !Array.isArray(exactArgv)
394
+ || exactArgv[0] !== executable
395
+ || evidence.cwd !== profileCommand.cwd
396
+ || JSON.stringify(exactArgv.slice(1)) !== JSON.stringify(profileCommand.argv)) {
397
+ return false;
398
+ }
399
+ return profileCommand.executable.includes("/")
400
+ ? profileCommand.executable === executable
401
+ : path.basename(executable) === profileCommand.executable
402
+ || path.basename(executable) === path.basename(profileCommand.executable);
403
+ }
404
+
405
+ function profileCommandMap(profilesDocument, selectedProfileIds, parentReceipt) {
406
+ const required = new Map();
407
+ const selected = profilesDocument.profiles.filter(profile => selectedProfileIds.includes(profile.id));
408
+ const withCommands = selected.filter(profile =>
409
+ Array.isArray(profile.commands) && profile.commands.length > 0);
410
+ if (withCommands.length === 0 && parentReceipt !== undefined) {
411
+ const parentProfiles = new Set(parentReceipt.selectedProfileIds ?? []);
412
+ if (selectedProfileIds.some(profileId => !parentProfiles.has(profileId))) {
413
+ throw new Error("Legacy repair command evidence does not cover selected profiles.");
414
+ }
415
+ for (const [index, receipt] of (parentReceipt.commandReceipts ?? []).entries()) {
416
+ const evidence = receipt?.evidence;
417
+ const executable = evidence?.launcher?.executable;
418
+ if (typeof executable !== "string"
419
+ || !path.isAbsolute(executable)
420
+ || !Array.isArray(evidence.argv)
421
+ || evidence.argv[0] !== executable
422
+ || typeof evidence.cwd !== "string") {
423
+ throw new Error("Legacy repair command evidence is not exactly replayable.");
424
+ }
425
+ required.set(`legacy-${index + 1}`, {
426
+ id: `legacy-${index + 1}`,
427
+ cwd: evidence.cwd,
428
+ executable,
429
+ argv: [...evidence.argv.slice(1)],
430
+ });
431
+ }
432
+ if (required.size === 0) throw new Error("Repair requires exact legacy command evidence.");
433
+ return required;
434
+ }
435
+ if (withCommands.length !== selected.length) {
436
+ throw new Error("Repair profile command metadata is incomplete.");
437
+ }
438
+ for (const command of selected.flatMap(profile => profile.commands ?? [])) {
439
+ safeIdentifier(command.id, "repair profile command id");
440
+ if (typeof command.cwd !== "string"
441
+ || typeof command.executable !== "string"
442
+ || command.executable.length === 0
443
+ || !Array.isArray(command.argv)
444
+ || command.argv.some(value => typeof value !== "string")) {
445
+ throw new Error(`Repair profile command is invalid: ${command.id}`);
446
+ }
447
+ const normalized = {
448
+ id: command.id,
449
+ cwd: command.cwd,
450
+ executable: command.executable,
451
+ argv: [...command.argv],
452
+ };
453
+ const existing = required.get(command.id);
454
+ if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
455
+ throw new Error(`Repair profile command id has conflicting definitions: ${command.id}`);
456
+ }
457
+ required.set(command.id, normalized);
458
+ }
459
+ if (required.size === 0) throw new Error("Repair requires profile-owned exact command metadata.");
460
+ return required;
461
+ }
462
+
463
+ function exactSortedIds(values) {
464
+ return [...values].sort((left, right) => left.localeCompare(right));
465
+ }
466
+
467
+ function assertRepairPlanSemantics({
468
+ workspaceRoot,
469
+ packetPath,
470
+ packet,
471
+ plan,
472
+ request,
473
+ repairControl,
474
+ profilesDocument,
475
+ selectedProfileIds,
476
+ activeParent,
477
+ currentGoal,
478
+ }) {
479
+ if (!isRecord(plan)
480
+ || plan.schemaVersion !== "OwlCodaRepairPlanV1"
481
+ || plan.runId !== packet.runId
482
+ || plan.planStatus !== "ready"
483
+ || !Array.isArray(plan.blockedCommands)
484
+ || plan.blockedCommands.length !== 0
485
+ || plan.authorizationGranted !== false) {
486
+ throw new Error("Repair finalize requires a ready RepairPlan with no blocked commands.");
487
+ }
488
+ const relativePacketPath = relativeToWorkspace(workspaceRoot, packetPath);
489
+ const packetSha256 = sha256(readFileSync(packetPath));
490
+ const fingerprint = packet.sourceFingerprint?.sha256;
491
+ if (!isRecord(plan.selectedPacket)
492
+ || plan.selectedPacket.path !== relativePacketPath
493
+ || request.deliveryPacketPath !== relativePacketPath
494
+ || plan.selectedPacket.packetId !== path.basename(packetPath, ".json")
495
+ || plan.selectedPacket.sha256 !== `sha256:${packetSha256}`
496
+ || plan.selectedPacket.sourceFingerprint !== `sha256:${fingerprint}`
497
+ || plan.currentSourceFingerprint !== `sha256:${fingerprint}`) {
498
+ throw new Error("Repair plan is not bound to the selected DeliveryPacket bytes.");
499
+ }
500
+ if (!isRecord(plan.invalidReceiptRef)
501
+ || plan.invalidReceiptRef.receiptId !== activeParent.receipt.receiptId
502
+ || plan.invalidReceiptRef.sha256 !== `sha256:${activeParent.receiptSha256}`
503
+ || plan.expectedSupersedes !== `sha256:${request.supersedesReceiptSha256}`
504
+ || request.supersedesReceiptSha256 !== activeParent.receiptSha256
505
+ || !REPAIRABLE_INVALID_REASONS.has(plan.invalidReason)) {
506
+ throw new Error("Repair plan is not bound to the active invalid receipt.");
507
+ }
508
+ if (plan.sourceStabilityGate !== "fail_on_any_source_drift_during_repair"
509
+ || !isRecord(plan.requiredTrust)
510
+ || plan.requiredTrust.riskMode !== currentGoal.risk.riskMode
511
+ || JSON.stringify(plan.requiredTrust.riskCategories) !== JSON.stringify(currentGoal.risk.riskCategories)
512
+ || plan.requiredTrust.implementationActor !== false
513
+ || plan.requiredTrust.independentReviewer !== false
514
+ || currentGoal.risk.riskMode === "full") {
515
+ throw new Error("Repair plan trust requirements do not match the bound standard/lightweight risk.");
516
+ }
517
+
518
+ const commands = profileCommandMap(
519
+ profilesDocument,
520
+ selectedProfileIds,
521
+ activeParent.receipt,
522
+ );
523
+ const reusableIds = plan.reusableCommandIds;
524
+ const pending = plan.pendingReplayCommands;
525
+ if (!Array.isArray(reusableIds)
526
+ || reusableIds.some(id => typeof id !== "string")
527
+ || new Set(reusableIds).size !== reusableIds.length
528
+ || !Array.isArray(pending)
529
+ || pending.some(command => !isRecord(command))) {
530
+ throw new Error("Repair plan command classification is malformed.");
531
+ }
532
+ const pendingIds = pending.map(command => command.commandId);
533
+ const allPlannedIds = [...reusableIds, ...pendingIds];
534
+ if (new Set(allPlannedIds).size !== allPlannedIds.length
535
+ || JSON.stringify(exactSortedIds(allPlannedIds))
536
+ !== JSON.stringify(exactSortedIds(commands.keys()))) {
537
+ throw new Error("Repair plan command classification does not exactly cover selected profiles.");
538
+ }
539
+ const bindings = new Map(repairControl.executableBindings.map(binding => [binding.commandId, binding]));
540
+ if (JSON.stringify(exactSortedIds(bindings.keys()))
541
+ !== JSON.stringify(exactSortedIds(commands.keys()))) {
542
+ throw new Error("Repair executable bindings do not exactly cover the plan.");
543
+ }
544
+ for (const command of pending) {
545
+ const configured = commands.get(command.commandId);
546
+ const binding = bindings.get(command.commandId);
547
+ if (!configured
548
+ || command.executable !== binding.executable
549
+ || command.cwd !== configured.cwd
550
+ || JSON.stringify(command.argv) !== JSON.stringify(configured.argv)
551
+ || !(configured.executable.includes("/")
552
+ ? configured.executable === command.executable
553
+ : path.basename(command.executable) === configured.executable
554
+ || path.basename(command.executable) === path.basename(configured.executable))) {
555
+ throw new Error(`Repair plan pending command does not match profile metadata: ${command.commandId}`);
556
+ }
557
+ }
558
+ return { reusableIds: exactSortedIds(reusableIds), pendingIds: exactSortedIds(pendingIds) };
559
+ }
560
+
561
+ function assertRepairCommandCoverage({
562
+ profilesDocument,
563
+ selectedProfileIds,
564
+ commandReceipts,
565
+ reusedCommandReceipts,
566
+ replayedCommandReceipts,
567
+ plan,
568
+ parentReceipt,
569
+ }) {
570
+ const required = [...profileCommandMap(
571
+ profilesDocument,
572
+ selectedProfileIds,
573
+ parentReceipt,
574
+ ).values()];
575
+ const matchedReceipts = new Set();
576
+ for (const command of required) {
577
+ const matches = commandReceipts.filter(receipt => exactCommandMatches(command, receipt));
578
+ if (matches.length !== 1) {
579
+ throw new Error(`Repair command coverage is incomplete or ambiguous: ${command.id}`);
580
+ }
581
+ matchedReceipts.add(matches[0]);
582
+ }
583
+ if (matchedReceipts.size !== commandReceipts.length) {
584
+ throw new Error("Repair receipt contains command evidence outside selected profile commands.");
585
+ }
586
+ const matchedCommandIds = receipts => receipts.map((receipt) => {
587
+ const matches = required.filter(command => exactCommandMatches(command, receipt));
588
+ if (matches.length !== 1) {
589
+ throw new Error(`Repair receipt command classification is ambiguous: ${receipt.id}`);
590
+ }
591
+ return matches[0].id;
592
+ });
593
+ if (JSON.stringify(exactSortedIds(matchedCommandIds(reusedCommandReceipts)))
594
+ !== JSON.stringify(exactSortedIds(plan.reusableCommandIds))
595
+ || JSON.stringify(exactSortedIds(matchedCommandIds(replayedCommandReceipts)))
596
+ !== JSON.stringify(exactSortedIds(plan.pendingReplayCommands.map(command => command.commandId)))) {
597
+ throw new Error("Repair receipt reuse/replay classes do not match the ready RepairPlan.");
598
+ }
599
+ }
600
+
601
+ export function runFinalize({ workspaceRoot, runId, request, hooks = {} }) {
602
+ validateRequest(request);
603
+ const { executionRoot, pinGate } = loadActiveExecution(workspaceRoot, runId);
604
+ if (pinGate.status !== "valid") return pinGate;
605
+ assertExecutionUnclosed(executionRoot, runId);
606
+ const packetPath = resolveExistingArtifact(
607
+ workspaceRoot,
608
+ request.deliveryPacketPath,
609
+ "deliveryPacketPath",
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 });
614
+ if (sourceGate.status !== "valid") {
615
+ return {
616
+ status: sourceGate.status,
617
+ exitCode: sourceGate.exitCode,
618
+ runId,
619
+ issues: sourceGate.issues,
620
+ authorizationGranted: false,
621
+ };
622
+ }
623
+ const profilesDocument = readJson(path.join(workspaceRoot, ".owlcoda/runkit/profiles.json"));
624
+ const profileImpact = resolveProfileImpactProjection({
625
+ changedPaths: changedPaths(packet),
626
+ profiles: profilesDocument.profiles,
627
+ detailed: request.supersedesReceiptSha256 !== undefined,
628
+ });
629
+ if (profileImpact.decision !== "targeted_profiles") {
630
+ return {
631
+ status: "full_profile_required",
632
+ exitCode: 2,
633
+ runId,
634
+ profileImpact,
635
+ authorizationGranted: false,
636
+ };
637
+ }
638
+ const contextFingerprint = verificationContextFingerprint(request.verificationContext);
639
+ const expectedFiles = packet.changedFiles?.files ?? packet.changedFiles?.wholeFileSha256;
640
+ const receiptsRoot = path.join(executionRoot, "verification-receipts");
641
+ const lineagePath = path.join(receiptsRoot, "receipt-lineage.json");
642
+ const existing = activeLineage(lineagePath);
643
+ const currentGoal = goalBinding(executionRoot);
644
+ if (!currentGoal.executionPlanGoalBindingValid) {
645
+ throw new Error("Execution plan does not bind the current goal contract.");
646
+ }
647
+ const reusedCommandReceipts = structuredClone(request.reusedCommandReceipts ?? []);
648
+ let repairPlan = null;
649
+ if (request.supersedesReceiptSha256 !== undefined) {
650
+ if (existing.active?.receiptSha256 !== request.supersedesReceiptSha256) {
651
+ throw new Error("Repair supersedesReceiptSha256 does not match the active receipt.");
652
+ }
653
+ const parentCommands = existing.active.receipt.commandReceipts ?? [];
654
+ for (const reused of reusedCommandReceipts) {
655
+ if (!parentCommands.some(candidate => JSON.stringify(candidate) === JSON.stringify(reused))) {
656
+ throw new Error("Reused command receipt is not an exact command receipt from the active parent.");
657
+ }
658
+ }
659
+ if (existing.active.receipt.verificationContextFingerprint !== contextFingerprint) {
660
+ throw new Error("Repair cannot reuse command receipts across verification-context drift.");
661
+ }
662
+ repairPlan = assertRepairControlCurrent({
663
+ workspaceRoot,
664
+ executionRoot,
665
+ packetPath,
666
+ packet,
667
+ repairControl: request.repairControl,
668
+ request,
669
+ profilesDocument,
670
+ selectedProfileIds: profileImpact.profileIds,
671
+ activeParent: existing.active,
672
+ }).plan;
673
+ }
674
+ const replayedCommandReceipts = request.snapshotPaths.map((snapshotPath) => snapshotCommandReceipt({
675
+ workspaceRoot,
676
+ runId,
677
+ snapshotPath,
678
+ contextFingerprint,
679
+ expectedFiles,
680
+ expectedFingerprint: sourceGate.recomputedFingerprint,
681
+ partialSourceBinding: request.supersedesReceiptSha256 !== undefined,
682
+ }));
683
+ const commandReceipts = [...reusedCommandReceipts, ...replayedCommandReceipts];
684
+ const commandIds = commandReceipts.map(receipt => receipt.id);
685
+ if (commandIds.some(id => typeof id !== "string" || id.length === 0)
686
+ || new Set(commandIds).size !== commandIds.length) {
687
+ throw new Error("Finalize command receipt ids must be unique non-empty strings.");
688
+ }
689
+ if (request.supersedesReceiptSha256 !== undefined) {
690
+ const coveredFiles = {};
691
+ for (const commandReceipt of commandReceipts) {
692
+ Object.assign(
693
+ coveredFiles,
694
+ commandMaterialMap({ workspaceRoot, commandReceipt, expectedFiles }),
695
+ );
696
+ }
697
+ if (JSON.stringify(sortedFileMap(coveredFiles))
698
+ !== JSON.stringify(sortedFileMap(expectedFiles))) {
699
+ throw new Error("Repair command material coverage does not exactly match the current DeliveryPacket.");
700
+ }
701
+ assertRepairCommandCoverage({
702
+ profilesDocument,
703
+ selectedProfileIds: profileImpact.profileIds,
704
+ commandReceipts,
705
+ reusedCommandReceipts,
706
+ replayedCommandReceipts,
707
+ plan: repairPlan,
708
+ parentReceipt: existing.active.receipt,
709
+ });
710
+ }
711
+ const receipt = {
712
+ schemaVersion: "OwlCodaRunKitVerificationReceiptV2",
713
+ runId,
714
+ receiptId: request.receiptId,
715
+ status: "passed",
716
+ sourceFingerprint: sourceGate.recomputedFingerprint,
717
+ verificationContextFingerprint: contextFingerprint,
718
+ goalContractSha256: currentGoal.goalContractSha256,
719
+ risk: structuredClone(currentGoal.risk),
720
+ selectedProfileIds: [...profileImpact.profileIds],
721
+ commandRuns: commandReceipts.length,
722
+ commandReceipts,
723
+ ...(request.supersedesReceiptSha256 === undefined
724
+ ? {}
725
+ : {
726
+ supersedesReceiptSha256: request.supersedesReceiptSha256,
727
+ repairControl: structuredClone(request.repairControl),
728
+ }),
729
+ };
730
+ const outputRoot = path.join(receiptsRoot, request.receiptId);
731
+ const receiptPath = path.join(outputRoot, "verification-receipt.json");
732
+ const lineage = nextLineage({
733
+ lineagePath,
734
+ receipt,
735
+ supersedesReceiptSha256: request.supersedesReceiptSha256,
736
+ });
737
+ const gateInput = {
738
+ contractVersion: "0.2",
739
+ receipts: lineage,
740
+ sourceGate,
741
+ profileImpact,
742
+ verificationContext: request.verificationContext,
743
+ };
744
+ const gate = validateVerificationReceiptGate(gateInput);
745
+ if (!gate.accepted) {
746
+ throw new Error(`Generated verification gate was not accepted: ${gate.issues.map((item) => item.message).join("; ")}`);
747
+ }
748
+ const assertCurrentBindings = () => {
749
+ if (request.repairControl !== undefined) {
750
+ assertRepairControlCurrent({
751
+ workspaceRoot,
752
+ executionRoot,
753
+ packetPath,
754
+ packet,
755
+ repairControl: request.repairControl,
756
+ request,
757
+ profilesDocument,
758
+ selectedProfileIds: profileImpact.profileIds,
759
+ activeParent: existing.active,
760
+ });
761
+ } else if (verifyDeliveryPacket({ workspaceRoot, packet }).status !== "valid") {
762
+ throw new Error("Source changed before receipt persistence.");
763
+ }
764
+ };
765
+ if (typeof hooks.beforeReceiptPersist === "function") hooks.beforeReceiptPersist();
766
+ assertCurrentBindings();
767
+ const sourceGatePath = path.join(outputRoot, "source-gate.json");
768
+ const profileImpactPath = path.join(outputRoot, "profile-impact.json");
769
+ const gateInputPath = path.join(outputRoot, "verification-gate-input.json");
770
+ const gateOutputPath = path.join(outputRoot, "verification-gate-output.json");
771
+ writeJsonExclusive(receiptPath, receipt);
772
+ writeJsonExclusive(sourceGatePath, sourceGate);
773
+ writeJsonExclusive(profileImpactPath, profileImpact);
774
+ writeJsonExclusive(gateInputPath, gateInput);
775
+ writeJsonExclusive(gateOutputPath, gate);
776
+ if (typeof hooks.beforeLineageActivate === "function") hooks.beforeLineageActivate();
777
+ assertCurrentBindings();
778
+ writeJsonAtomically(lineagePath, lineage);
779
+ return {
780
+ status: gate.decision,
781
+ exitCode: 0,
782
+ runId,
783
+ receiptPath: relativeToWorkspace(workspaceRoot, receiptPath),
784
+ lineagePath: relativeToWorkspace(workspaceRoot, lineagePath),
785
+ gateInputPath: relativeToWorkspace(workspaceRoot, gateInputPath),
786
+ gateOutputPath: relativeToWorkspace(workspaceRoot, gateOutputPath),
787
+ activeReceiptSha256: gate.activeReceiptSha256,
788
+ authorizationGranted: false,
789
+ };
790
+ }