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.
Files changed (76) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +274 -66
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
  4. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
  6. package/package.json +2 -2
  7. package/packages/attest/distribution-manifest-v1.json +10 -0
  8. package/packages/attest/src/quick.mjs +10 -0
  9. package/registry-release-policy-v1.json +8 -2
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/assurance-router.mjs +119 -0
  12. package/scripts/runkit-contract/core-contract.mjs +250 -115
  13. package/scripts/runkit-contract/core-successor.mjs +2043 -0
  14. package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
  15. package/scripts/runkit-contract/finalize.mjs +242 -21
  16. package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
  17. package/scripts/runkit-contract/formal-workflow.mjs +850 -0
  18. package/scripts/runkit-contract/human-status.mjs +553 -0
  19. package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
  20. package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
  21. package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
  22. package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
  23. package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
  24. package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
  25. package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
  26. package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
  27. package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
  28. package/scripts/runkit-contract/project-control-parser.mjs +830 -0
  29. package/scripts/runkit-contract/project-control-state.mjs +28 -0
  30. package/scripts/runkit-contract/provenance-common.mjs +19 -2
  31. package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
  32. package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
  33. package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
  36. package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
  37. package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
  38. package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
  39. package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
  40. package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
  41. package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
  42. package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
  43. package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
  44. package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
  45. package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
  46. package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
  47. package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
  48. package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
  49. package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
  50. package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
  51. package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
  52. package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
  53. package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
  54. package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
  55. package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
  56. package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
  57. package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
  58. package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
  59. package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
  60. package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
  61. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
  62. package/scripts/runkit-contract/snapshot.mjs +1 -1
  63. package/scripts/runkit-contract/source-candidate.mjs +1382 -0
  64. package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
  65. package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
  66. package/scripts/runkit-contract/templates/assurance-request.json +13 -0
  67. package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
  68. package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
  69. package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
  70. package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
  71. package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
  72. package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
  73. package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
  74. package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
  75. package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
  76. package/scripts/runkit-contract/verification-receipt-gate.mjs +28 -0
@@ -0,0 +1,28 @@
1
+ import {
2
+ RUNTIME_ROOT,
3
+ acceptedCloseoutVerificationIssues,
4
+ currentCoreIdentity,
5
+ validateCoreArtifact,
6
+ validateExecutionPin,
7
+ validateLeaseOwnedPaths,
8
+ } from "./core-contract.mjs";
9
+ import { parseProjectControlState } from "./project-control-parser.mjs";
10
+
11
+ const PARSER_CONTRACT = Object.freeze({
12
+ runtimeRoot: RUNTIME_ROOT,
13
+ acceptedCloseoutVerificationIssues,
14
+ validateCoreArtifact,
15
+ validateExecutionPin,
16
+ validateLeaseOwnedPaths,
17
+ });
18
+
19
+ export function inspectProjectControlState({
20
+ workspaceRoot,
21
+ currentCore = currentCoreIdentity(),
22
+ } = {}) {
23
+ return parseProjectControlState({
24
+ workspaceRoot,
25
+ currentCore,
26
+ contract: PARSER_CONTRACT,
27
+ });
28
+ }
@@ -97,8 +97,25 @@ export function safeRelativePath(value, label, { allowDot = false } = {}) {
97
97
 
98
98
  export function resolveExistingArtifact(workspaceRoot, value, label) {
99
99
  const relativePath = safeRelativePath(value, label);
100
- const candidate = path.resolve(workspaceRoot, relativePath);
101
- if (!withinRoot(workspaceRoot, candidate)) throw new Error(`${label} escapes the workspace.`);
100
+ const root = realpathSync(workspaceRoot);
101
+ const candidate = path.resolve(root, relativePath);
102
+ if (!withinRoot(root, candidate)) throw new Error(`${label} escapes the workspace.`);
103
+ let current = root;
104
+ const segments = relativePath.split("/");
105
+ for (const [index, segment] of segments.entries()) {
106
+ current = path.join(current, segment);
107
+ const stat = lstatSync(current);
108
+ if (stat.isSymbolicLink()) {
109
+ throw new Error(`${label} must not traverse a symlink.`);
110
+ }
111
+ if (index < segments.length - 1 && !stat.isDirectory()) {
112
+ throw new Error(`${label} has a non-directory ancestor.`);
113
+ }
114
+ const resolved = realpathSync(current);
115
+ if (!withinRoot(root, resolved)) {
116
+ throw new Error(`${label} resolves outside the workspace.`);
117
+ }
118
+ }
102
119
  return realpathSync(candidate);
103
120
  }
104
121
 
@@ -10,22 +10,189 @@ import {
10
10
  relativeToWorkspace,
11
11
  repositoryActionsFalse,
12
12
  resolveExistingArtifact,
13
+ safeIdentifier,
14
+ safeRelativePath,
13
15
  sha256,
14
16
  writeJsonExclusive,
15
17
  } from "./provenance-common.mjs";
16
18
  import { resolveProfileImpactProjection } from "./profile-impact.mjs";
17
19
  import { verifySnapshotEvidence, verifySnapshotFreshness } from "./snapshot.mjs";
20
+ import { verifySourceCandidateV2 } from "./source-candidate.mjs";
18
21
  import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
19
22
  import { validateVerificationReceiptGate } from "./verification-receipt-gate.mjs";
20
23
 
21
- const REQUEST_KEYS = [
24
+ const REQUEST_KEYS_V1 = [
22
25
  "schemaVersion",
23
26
  "deliveryPacketPath",
24
27
  "verificationGateInputPath",
25
28
  "roots",
26
29
  ];
30
+ const REQUEST_KEYS_V2 = [
31
+ ...REQUEST_KEYS_V1,
32
+ "sourceCandidatePath",
33
+ ];
34
+ const RECEIPT_COMMON_KEYS = [
35
+ "schemaVersion",
36
+ "runId",
37
+ "status",
38
+ "sourceFingerprint",
39
+ "activeReceiptSha256",
40
+ "verificationGateInputPath",
41
+ "verificationGateInputSha256",
42
+ "closeoutReceiptPath",
43
+ "closeoutReceiptSha256",
44
+ "releasedLeaseIds",
45
+ "roots",
46
+ "repositoryActions",
47
+ "authorizationGranted",
48
+ ];
49
+ const RECEIPT_ROOT_KEYS = [
50
+ "role",
51
+ "snapshotPath",
52
+ "snapshotSha256",
53
+ "targetRoot",
54
+ "head",
55
+ "manifestFingerprint",
56
+ ];
57
+ const RECEIPT_SOURCE_ARTIFACT_KEYS = [
58
+ "kind",
59
+ "path",
60
+ "sha256",
61
+ "sourceFingerprint",
62
+ ];
63
+ const PLAIN_SHA256 = /^[a-f0-9]{64}$/u;
64
+ const GIT_OBJECT_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
65
+
66
+ function exactFalseRepositoryActions(value) {
67
+ const expected = repositoryActionsFalse();
68
+ return isRecord(value)
69
+ && Object.keys(value).length === Object.keys(expected).length
70
+ && Object.keys(expected).every((key) => value[key] === false);
71
+ }
72
+
73
+ export function validateReadyForCommitReceipt(receipt) {
74
+ const v1 = receipt?.schemaVersion
75
+ === "OwlCodaRunKitReadyForCommitReceiptV1";
76
+ const v2 = receipt?.schemaVersion
77
+ === "OwlCodaRunKitReadyForCommitReceiptV2";
78
+ if (!v1 && !v2) {
79
+ throw new Error(
80
+ "Ready-for-commit receipt has an unsupported schemaVersion.",
81
+ );
82
+ }
83
+ assertAllowedKeys(
84
+ receipt,
85
+ "Ready-for-commit receipt",
86
+ v1
87
+ ? [...RECEIPT_COMMON_KEYS, "deliveryPacketPath", "deliveryPacketSha256"]
88
+ : [...RECEIPT_COMMON_KEYS, "sourceArtifact"],
89
+ );
90
+ if (
91
+ safeIdentifier(receipt.runId, "Ready-for-commit receipt runId")
92
+ !== receipt.runId
93
+ || receipt.status !== "ready_for_commit"
94
+ || !PLAIN_SHA256.test(receipt.sourceFingerprint ?? "")
95
+ || !PLAIN_SHA256.test(receipt.activeReceiptSha256 ?? "")
96
+ || !PLAIN_SHA256.test(receipt.verificationGateInputSha256 ?? "")
97
+ || !PLAIN_SHA256.test(receipt.closeoutReceiptSha256 ?? "")
98
+ || receipt.authorizationGranted !== false
99
+ || !exactFalseRepositoryActions(receipt.repositoryActions)
100
+ ) {
101
+ throw new Error("Ready-for-commit receipt common fields are invalid.");
102
+ }
103
+ safeRelativePath(
104
+ receipt.verificationGateInputPath,
105
+ "Ready-for-commit receipt verificationGateInputPath",
106
+ );
107
+ safeRelativePath(
108
+ receipt.closeoutReceiptPath,
109
+ "Ready-for-commit receipt closeoutReceiptPath",
110
+ );
111
+ if (
112
+ !Array.isArray(receipt.releasedLeaseIds)
113
+ || receipt.releasedLeaseIds.length === 0
114
+ || new Set(receipt.releasedLeaseIds).size
115
+ !== receipt.releasedLeaseIds.length
116
+ || receipt.releasedLeaseIds.some((workItemId) => {
117
+ try {
118
+ return safeIdentifier(
119
+ workItemId,
120
+ "Ready-for-commit receipt released lease",
121
+ ) !== workItemId;
122
+ } catch {
123
+ return true;
124
+ }
125
+ })
126
+ || JSON.stringify(receipt.releasedLeaseIds)
127
+ !== JSON.stringify([...receipt.releasedLeaseIds].sort())
128
+ ) {
129
+ throw new Error("Ready-for-commit receipt released leases are invalid.");
130
+ }
131
+ if (!Array.isArray(receipt.roots) || receipt.roots.length === 0) {
132
+ throw new Error("Ready-for-commit receipt roots are invalid.");
133
+ }
134
+ const roles = new Set();
135
+ for (const root of receipt.roots) {
136
+ assertAllowedKeys(root, "Ready-for-commit receipt root", RECEIPT_ROOT_KEYS);
137
+ if (
138
+ typeof root.role !== "string"
139
+ || root.role.length === 0
140
+ || roles.has(root.role)
141
+ || !PLAIN_SHA256.test(root.snapshotSha256 ?? "")
142
+ || !path.isAbsolute(root.targetRoot ?? "")
143
+ || !GIT_OBJECT_ID.test(root.head ?? "")
144
+ || !PLAIN_SHA256.test(root.manifestFingerprint ?? "")
145
+ ) {
146
+ throw new Error("Ready-for-commit receipt root is invalid.");
147
+ }
148
+ safeRelativePath(
149
+ root.snapshotPath,
150
+ "Ready-for-commit receipt root snapshotPath",
151
+ );
152
+ roles.add(root.role);
153
+ }
154
+ if (!roles.has("candidate")) {
155
+ throw new Error(
156
+ "Ready-for-commit receipt requires a candidate root.",
157
+ );
158
+ }
159
+ if (v1) {
160
+ safeRelativePath(
161
+ receipt.deliveryPacketPath,
162
+ "Ready-for-commit receipt deliveryPacketPath",
163
+ );
164
+ if (!PLAIN_SHA256.test(receipt.deliveryPacketSha256 ?? "")) {
165
+ throw new Error(
166
+ "Ready-for-commit receipt delivery packet hash is invalid.",
167
+ );
168
+ }
169
+ } else {
170
+ assertAllowedKeys(
171
+ receipt.sourceArtifact,
172
+ "Ready-for-commit receipt sourceArtifact",
173
+ RECEIPT_SOURCE_ARTIFACT_KEYS,
174
+ );
175
+ if (
176
+ !["delivery_packet_v1", "source_candidate_v2"]
177
+ .includes(receipt.sourceArtifact.kind)
178
+ || !PLAIN_SHA256.test(receipt.sourceArtifact.sha256 ?? "")
179
+ || !PLAIN_SHA256.test(
180
+ receipt.sourceArtifact.sourceFingerprint ?? "",
181
+ )
182
+ ) {
183
+ throw new Error(
184
+ "Ready-for-commit receipt sourceArtifact is invalid.",
185
+ );
186
+ }
187
+ safeRelativePath(
188
+ receipt.sourceArtifact.path,
189
+ "Ready-for-commit receipt sourceArtifact path",
190
+ );
191
+ }
192
+ return { v1, v2 };
193
+ }
27
194
 
28
- function changedPaths(packet) {
195
+ function deliveryPacketChangedPaths(packet) {
29
196
  const files = packet?.changedFiles?.files ?? packet?.changedFiles?.wholeFileSha256;
30
197
  if (!isRecord(files) || Object.keys(files).length === 0) {
31
198
  throw new Error("Delivery packet does not declare changed files.");
@@ -33,14 +200,39 @@ function changedPaths(packet) {
33
200
  return Object.keys(files).sort();
34
201
  }
35
202
 
203
+ function sourceCandidateChangedPaths(candidate) {
204
+ const entries = candidate?.sourceManifest?.entries;
205
+ if (!Array.isArray(entries) || entries.length === 0) {
206
+ throw new Error("Source candidate does not declare changed files.");
207
+ }
208
+ return [...new Set(entries.flatMap((entry) => (
209
+ entry.previousPath ? [entry.path, entry.previousPath] : [entry.path]
210
+ )))].sort();
211
+ }
212
+
36
213
  function validateRequest(request) {
37
- assertAllowedKeys(request, "Ready-for-commit request", REQUEST_KEYS);
38
- if (request.schemaVersion !== "OwlCodaRunKitReadyForCommitRequestV1") {
214
+ const v1 = request.schemaVersion === "OwlCodaRunKitReadyForCommitRequestV1";
215
+ const v2 = request.schemaVersion === "OwlCodaRunKitReadyForCommitRequestV2";
216
+ if (!v1 && !v2) {
39
217
  throw new Error("Unsupported ready-for-commit request schemaVersion.");
40
218
  }
41
- if (typeof request.deliveryPacketPath !== "string" || request.deliveryPacketPath.length === 0) {
219
+ assertAllowedKeys(
220
+ request,
221
+ "Ready-for-commit request",
222
+ v1 ? REQUEST_KEYS_V1 : REQUEST_KEYS_V2,
223
+ );
224
+ const hasDeliveryPacket = typeof request.deliveryPacketPath === "string"
225
+ && request.deliveryPacketPath.length > 0;
226
+ const hasSourceCandidate = typeof request.sourceCandidatePath === "string"
227
+ && request.sourceCandidatePath.length > 0;
228
+ if (v1 && !hasDeliveryPacket) {
42
229
  throw new Error("Ready-for-commit request requires deliveryPacketPath.");
43
230
  }
231
+ if (v2 && hasDeliveryPacket === hasSourceCandidate) {
232
+ throw new Error(
233
+ "Ready-for-commit V2 requires exactly one deliveryPacketPath or sourceCandidatePath.",
234
+ );
235
+ }
44
236
  if (typeof request.verificationGateInputPath !== "string" || request.verificationGateInputPath.length === 0) {
45
237
  throw new Error("Ready-for-commit request requires verificationGateInputPath.");
46
238
  }
@@ -60,6 +252,7 @@ function validateRequest(request) {
60
252
  roles.add(root.role);
61
253
  }
62
254
  if (!roles.has("candidate")) throw new Error("Ready-for-commit requires a candidate root snapshot.");
255
+ return { v1, v2, hasDeliveryPacket, hasSourceCandidate };
63
256
  }
64
257
 
65
258
  function verifyReleasedLeases(executionRoot) {
@@ -74,7 +267,22 @@ function verifyReleasedLeases(executionRoot) {
74
267
  return leases.map((lease) => lease.workItemId).sort();
75
268
  }
76
269
 
77
- function verifyCloseout({ executionRoot, runId, gateInputBytes, gate }) {
270
+ function sameSourceArtifact(left, right) {
271
+ return left?.kind === right.kind
272
+ && left?.runId === right.runId
273
+ && left?.path === right.path
274
+ && left?.sha256 === right.sha256
275
+ && left?.sourceFingerprint === right.sourceFingerprint;
276
+ }
277
+
278
+ function verifyCloseout({
279
+ executionRoot,
280
+ runId,
281
+ gateInputBytes,
282
+ gate,
283
+ sourceArtifact,
284
+ requireSourceArtifact,
285
+ }) {
78
286
  const closeoutPath = path.join(executionRoot, "closeout-receipt.json");
79
287
  if (!existsSync(closeoutPath)) throw new Error("Accepted closeout receipt is required.");
80
288
  const closeout = readJson(closeoutPath);
@@ -95,23 +303,94 @@ function verifyCloseout({ executionRoot, runId, gateInputBytes, gate }) {
95
303
  if (payload.verification?.activeReceiptSha256 !== gate.activeReceiptSha256) {
96
304
  throw new Error("Closeout active receipt does not match the current verification gate.");
97
305
  }
306
+ if (
307
+ requireSourceArtifact
308
+ && !sameSourceArtifact(payload.verification?.sourceArtifact, sourceArtifact)
309
+ ) {
310
+ throw new Error("Closeout source artifact does not match the requested source.");
311
+ }
98
312
  return { closeoutPath, closeout };
99
313
  }
100
314
 
315
+ function loadReadySource({ workspaceRoot, runId, request, requestVersion }) {
316
+ if (request.deliveryPacketPath !== undefined) {
317
+ const sourcePath = resolveExistingArtifact(
318
+ workspaceRoot,
319
+ request.deliveryPacketPath,
320
+ "deliveryPacketPath",
321
+ );
322
+ const source = readJson(sourcePath);
323
+ if (source.runId !== runId) {
324
+ throw new Error("Delivery packet runId does not match the execution.");
325
+ }
326
+ const sourceGate = verifyDeliveryPacket({ workspaceRoot, packet: source });
327
+ return {
328
+ source,
329
+ sourcePath,
330
+ sourceGate,
331
+ changedPaths: deliveryPacketChangedPaths(source),
332
+ sourceArtifact: {
333
+ kind: "delivery_packet_v1",
334
+ runId,
335
+ path: request.deliveryPacketPath,
336
+ sha256: sha256(readFileSync(sourcePath)),
337
+ sourceFingerprint: sourceGate.recomputedFingerprint,
338
+ },
339
+ legacyV1: requestVersion.v1,
340
+ };
341
+ }
342
+
343
+ const sourcePath = resolveExistingArtifact(
344
+ workspaceRoot,
345
+ request.sourceCandidatePath,
346
+ "sourceCandidatePath",
347
+ );
348
+ const source = readJson(sourcePath);
349
+ if (source.runId !== runId) {
350
+ throw new Error("Source candidate runId does not match the execution.");
351
+ }
352
+ const candidateGate = verifySourceCandidateV2({
353
+ workspaceRoot,
354
+ candidatePath: request.sourceCandidatePath,
355
+ });
356
+ return {
357
+ source,
358
+ sourcePath,
359
+ sourceGate: {
360
+ status: candidateGate.status,
361
+ exitCode: candidateGate.exitCode,
362
+ recomputedFingerprint: candidateGate.sourceFingerprint,
363
+ issues: candidateGate.issueCodes ?? [],
364
+ },
365
+ changedPaths: sourceCandidateChangedPaths(source),
366
+ sourceArtifact: {
367
+ kind: "source_candidate_v2",
368
+ runId,
369
+ path: request.sourceCandidatePath,
370
+ sha256: sha256(readFileSync(sourcePath)),
371
+ sourceFingerprint: candidateGate.sourceFingerprint,
372
+ },
373
+ legacyV1: false,
374
+ };
375
+ }
376
+
101
377
  export function runReadyForCommit({ workspaceRoot, runId, request }) {
102
- validateRequest(request);
378
+ const requestVersion = validateRequest(request);
103
379
  const { executionRoot, pinGate } = loadActiveExecution(workspaceRoot, runId);
104
380
  if (pinGate.status !== "valid") return pinGate;
105
- const packetPath = resolveExistingArtifact(workspaceRoot, request.deliveryPacketPath, "deliveryPacketPath");
106
- const packet = readJson(packetPath);
107
- if (packet.runId !== runId) throw new Error("Delivery packet runId does not match the execution.");
108
- const sourceGate = verifyDeliveryPacket({ workspaceRoot, packet });
381
+ const sourceBinding = loadReadySource({
382
+ workspaceRoot,
383
+ runId,
384
+ request,
385
+ requestVersion,
386
+ });
387
+ const sourceGate = sourceBinding.sourceGate;
109
388
  if (sourceGate.status !== "valid") {
110
389
  return {
111
390
  status: sourceGate.status,
112
391
  exitCode: sourceGate.exitCode,
113
392
  runId,
114
- issues: sourceGate.issues,
393
+ issues: sourceGate.issues ?? [],
115
394
  authorizationGranted: false,
116
395
  };
117
396
  }
@@ -129,12 +408,23 @@ export function runReadyForCommit({ workspaceRoot, runId, request }) {
129
408
  if (gate.sourceFingerprint !== sourceGate.recomputedFingerprint) {
130
409
  throw new Error("Verification gate source fingerprint is stale.");
131
410
  }
411
+ if (
412
+ requestVersion.v2
413
+ && !sameSourceArtifact(
414
+ gate.lineage.active.receipt.sourceArtifact,
415
+ sourceBinding.sourceArtifact,
416
+ )
417
+ ) {
418
+ throw new Error(
419
+ "Verification gate source artifact does not match the requested source.",
420
+ );
421
+ }
132
422
  const profiles = readJson(path.join(workspaceRoot, ".owlcoda/runkit/profiles.json")).profiles;
133
423
  const detailedProfileImpact = isRecord(gateInput.profileImpact)
134
424
  && ("directProfileIds" in gateInput.profileImpact
135
425
  || "transitiveProfileIds" in gateInput.profileImpact);
136
426
  const currentProfileImpact = resolveProfileImpactProjection({
137
- changedPaths: changedPaths(packet),
427
+ changedPaths: sourceBinding.changedPaths,
138
428
  profiles,
139
429
  detailed: detailedProfileImpact,
140
430
  });
@@ -142,7 +432,14 @@ export function runReadyForCommit({ workspaceRoot, runId, request }) {
142
432
  throw new Error("Verification profile impact is stale.");
143
433
  }
144
434
  const releasedLeaseIds = verifyReleasedLeases(executionRoot);
145
- const { closeoutPath } = verifyCloseout({ executionRoot, runId, gateInputBytes, gate });
435
+ const { closeoutPath } = verifyCloseout({
436
+ executionRoot,
437
+ runId,
438
+ gateInputBytes,
439
+ gate,
440
+ sourceArtifact: sourceBinding.sourceArtifact,
441
+ requireSourceArtifact: requestVersion.v2,
442
+ });
146
443
  const roots = request.roots.map((root) => {
147
444
  const snapshotPath = resolveExistingArtifact(workspaceRoot, root.snapshotPath, "root snapshotPath");
148
445
  const snapshot = readJson(snapshotPath);
@@ -160,16 +457,13 @@ export function runReadyForCommit({ workspaceRoot, runId, request }) {
160
457
  };
161
458
  });
162
459
  const readyPath = path.join(executionRoot, "READY_FOR_COMMIT_RECEIPT.json");
163
- const receipt = {
164
- schemaVersion: "OwlCodaRunKitReadyForCommitReceiptV1",
460
+ const commonReceipt = {
165
461
  runId,
166
462
  status: "ready_for_commit",
167
463
  sourceFingerprint: sourceGate.recomputedFingerprint,
168
464
  activeReceiptSha256: gate.activeReceiptSha256,
169
465
  verificationGateInputPath: request.verificationGateInputPath,
170
466
  verificationGateInputSha256: sha256(gateInputBytes),
171
- deliveryPacketPath: request.deliveryPacketPath,
172
- deliveryPacketSha256: sha256(readFileSync(packetPath)),
173
467
  closeoutReceiptPath: relativeToWorkspace(workspaceRoot, closeoutPath),
174
468
  closeoutReceiptSha256: sha256(readFileSync(closeoutPath)),
175
469
  releasedLeaseIds,
@@ -177,6 +471,24 @@ export function runReadyForCommit({ workspaceRoot, runId, request }) {
177
471
  repositoryActions: repositoryActionsFalse(),
178
472
  authorizationGranted: false,
179
473
  };
474
+ const receipt = requestVersion.v1
475
+ ? {
476
+ schemaVersion: "OwlCodaRunKitReadyForCommitReceiptV1",
477
+ ...commonReceipt,
478
+ deliveryPacketPath: request.deliveryPacketPath,
479
+ deliveryPacketSha256: sourceBinding.sourceArtifact.sha256,
480
+ }
481
+ : {
482
+ schemaVersion: "OwlCodaRunKitReadyForCommitReceiptV2",
483
+ ...commonReceipt,
484
+ sourceArtifact: {
485
+ kind: sourceBinding.sourceArtifact.kind,
486
+ path: sourceBinding.sourceArtifact.path,
487
+ sha256: sourceBinding.sourceArtifact.sha256,
488
+ sourceFingerprint: sourceBinding.sourceArtifact.sourceFingerprint,
489
+ },
490
+ };
491
+ validateReadyForCommitReceipt(receipt);
180
492
  writeJsonExclusive(readyPath, receipt);
181
493
  return {
182
494
  status: "ready_for_commit",