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
|
@@ -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
|
|
101
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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({
|
|
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
|
|
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",
|