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,850 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
acceptedCloseoutVerificationIssues,
|
|
12
|
+
validateCoreArtifact,
|
|
13
|
+
} from "./core-contract.mjs";
|
|
14
|
+
import { runFinalize } from "./finalize.mjs";
|
|
15
|
+
import { derivedVerificationContext } from "./lifecycle-orchestration.mjs";
|
|
16
|
+
import { withControlTransaction } from "./lease-lifecycle.mjs";
|
|
17
|
+
import {
|
|
18
|
+
loadActiveExecution,
|
|
19
|
+
readJson,
|
|
20
|
+
relativeToWorkspace,
|
|
21
|
+
safeIdentifier,
|
|
22
|
+
safeRelativePath,
|
|
23
|
+
sha256,
|
|
24
|
+
writeJsonExclusiveAtomically,
|
|
25
|
+
} from "./provenance-common.mjs";
|
|
26
|
+
import {
|
|
27
|
+
runSnapshot,
|
|
28
|
+
verifySnapshotEvidence,
|
|
29
|
+
} from "./snapshot.mjs";
|
|
30
|
+
import {
|
|
31
|
+
freezeSourceCandidateWithinControlTransactionV2,
|
|
32
|
+
verifySourceCandidateV2,
|
|
33
|
+
} from "./source-candidate.mjs";
|
|
34
|
+
import { validateReceiptLineage } from "./receipt-lineage.mjs";
|
|
35
|
+
import { verificationContextFingerprint } from "./verification-context.mjs";
|
|
36
|
+
|
|
37
|
+
const FORBIDDEN_EXTERNAL_TOOLS = new Set([
|
|
38
|
+
"curl",
|
|
39
|
+
"ftp",
|
|
40
|
+
"nc",
|
|
41
|
+
"ncat",
|
|
42
|
+
"rsync",
|
|
43
|
+
"scp",
|
|
44
|
+
"sftp",
|
|
45
|
+
"ssh",
|
|
46
|
+
"wget",
|
|
47
|
+
]);
|
|
48
|
+
const FORBIDDEN_WRAPPERS = new Set([
|
|
49
|
+
"bash",
|
|
50
|
+
"cmd",
|
|
51
|
+
"env",
|
|
52
|
+
"fish",
|
|
53
|
+
"osascript",
|
|
54
|
+
"powershell",
|
|
55
|
+
"pwsh",
|
|
56
|
+
"sh",
|
|
57
|
+
"zsh",
|
|
58
|
+
]);
|
|
59
|
+
const FORBIDDEN_GIT_ACTIONS = new Set([
|
|
60
|
+
"add",
|
|
61
|
+
"checkout",
|
|
62
|
+
"clean",
|
|
63
|
+
"commit",
|
|
64
|
+
"merge",
|
|
65
|
+
"pull",
|
|
66
|
+
"push",
|
|
67
|
+
"rebase",
|
|
68
|
+
"reset",
|
|
69
|
+
"restore",
|
|
70
|
+
"revert",
|
|
71
|
+
"switch",
|
|
72
|
+
"tag",
|
|
73
|
+
]);
|
|
74
|
+
const FORBIDDEN_PACKAGE_ACTIONS = new Set([
|
|
75
|
+
"add",
|
|
76
|
+
"deprecate",
|
|
77
|
+
"dist-tag",
|
|
78
|
+
"install",
|
|
79
|
+
"login",
|
|
80
|
+
"logout",
|
|
81
|
+
"owner",
|
|
82
|
+
"publish",
|
|
83
|
+
"remove",
|
|
84
|
+
"uninstall",
|
|
85
|
+
"unpublish",
|
|
86
|
+
"update",
|
|
87
|
+
"version",
|
|
88
|
+
]);
|
|
89
|
+
const LEGACY_FORBIDDEN_GIT_ACTIONS = new Set(["push", "tag"]);
|
|
90
|
+
const LEGACY_FORBIDDEN_PACKAGE_ACTIONS = new Set(["publish", "unpublish"]);
|
|
91
|
+
const KNOWN_DEPLOYMENT_TOOLS = new Set([
|
|
92
|
+
"firebase",
|
|
93
|
+
"flyctl",
|
|
94
|
+
"netlify",
|
|
95
|
+
"vercel",
|
|
96
|
+
"wrangler",
|
|
97
|
+
]);
|
|
98
|
+
const PACKAGE_TOOLS = new Set(["bun", "npm", "npx", "pnpm", "yarn"]);
|
|
99
|
+
|
|
100
|
+
function checkRoot(executionRoot) {
|
|
101
|
+
return path.join(executionRoot, "formal-checks");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function canonicalJson(value) {
|
|
105
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
106
|
+
if (value && typeof value === "object") {
|
|
107
|
+
return `{${Object.keys(value).sort()
|
|
108
|
+
.map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
109
|
+
.join(",")}}`;
|
|
110
|
+
}
|
|
111
|
+
const encoded = JSON.stringify(value);
|
|
112
|
+
if (encoded === undefined) {
|
|
113
|
+
throw new Error("Formal artifact is not canonical JSON.");
|
|
114
|
+
}
|
|
115
|
+
return encoded;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readRegularJson(filePath, label) {
|
|
119
|
+
const stat = lstatSync(filePath);
|
|
120
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
121
|
+
throw new Error(`${label} must be a regular file.`);
|
|
122
|
+
}
|
|
123
|
+
if (realpathSync(filePath) !== path.resolve(filePath)) {
|
|
124
|
+
throw new Error(`${label} must not traverse a symlink.`);
|
|
125
|
+
}
|
|
126
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function writeOrResumeExactJson(filePath, value, label) {
|
|
130
|
+
if (!existsSync(filePath)) {
|
|
131
|
+
writeJsonExclusiveAtomically(filePath, value);
|
|
132
|
+
return { value, resumed: false };
|
|
133
|
+
}
|
|
134
|
+
const existing = readRegularJson(filePath, label);
|
|
135
|
+
if (canonicalJson(existing) !== canonicalJson(value)) {
|
|
136
|
+
throw new Error(`Existing ${label} differs from the resumed command.`);
|
|
137
|
+
}
|
|
138
|
+
return { value: existing, resumed: true };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function withinRoot(root, candidate) {
|
|
142
|
+
const relative = path.relative(root, candidate);
|
|
143
|
+
return relative === ""
|
|
144
|
+
|| (!relative.startsWith(`..${path.sep}`)
|
|
145
|
+
&& relative !== ".."
|
|
146
|
+
&& !path.isAbsolute(relative));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function regularExecutable(executable) {
|
|
150
|
+
if (typeof executable !== "string" || !path.isAbsolute(executable)) {
|
|
151
|
+
throw new Error("Formal check executable must be an absolute path.");
|
|
152
|
+
}
|
|
153
|
+
const stat = lstatSync(executable);
|
|
154
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
155
|
+
throw new Error("Formal check executable must be a regular file, not a symlink.");
|
|
156
|
+
}
|
|
157
|
+
return realpathSync(executable);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function executableName(executable) {
|
|
161
|
+
return path.basename(executable)
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
.replace(/\.(?:cmd|exe)$/u, "");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function effectiveTool(executable, argv) {
|
|
167
|
+
const direct = executableName(executable);
|
|
168
|
+
if (
|
|
169
|
+
direct === "node"
|
|
170
|
+
&& typeof argv[0] === "string"
|
|
171
|
+
&& /^(?:npm-cli|pnpm|yarn)(?:\.[cm]?js)?$/u.test(
|
|
172
|
+
path.basename(argv[0]).toLowerCase(),
|
|
173
|
+
)
|
|
174
|
+
) {
|
|
175
|
+
const launcher = path.basename(argv[0]).toLowerCase();
|
|
176
|
+
return {
|
|
177
|
+
name: launcher.startsWith("npm")
|
|
178
|
+
? "npm"
|
|
179
|
+
: launcher.startsWith("pnpm")
|
|
180
|
+
? "pnpm"
|
|
181
|
+
: "yarn",
|
|
182
|
+
argv: argv.slice(1),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return { name: direct, argv };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function forbiddenFormalCommand(executable, argv) {
|
|
189
|
+
const externalSideEffect = forbiddenLegacyVerificationSideEffect(
|
|
190
|
+
executable,
|
|
191
|
+
argv,
|
|
192
|
+
);
|
|
193
|
+
if (externalSideEffect) return externalSideEffect;
|
|
194
|
+
const tool = effectiveTool(executable, argv);
|
|
195
|
+
if (FORBIDDEN_WRAPPERS.has(tool.name)) return tool.name;
|
|
196
|
+
if (tool.name === "node" && tool.argv.some((argument) =>
|
|
197
|
+
new Set(["-e", "--eval", "-p", "--print"]).has(argument))) {
|
|
198
|
+
return "node-inline-code";
|
|
199
|
+
}
|
|
200
|
+
const action = tool.argv
|
|
201
|
+
.map((argument) => argument.toLowerCase())
|
|
202
|
+
.find((argument) => (
|
|
203
|
+
tool.name === "git"
|
|
204
|
+
? FORBIDDEN_GIT_ACTIONS.has(argument)
|
|
205
|
+
: FORBIDDEN_PACKAGE_ACTIONS.has(argument)
|
|
206
|
+
));
|
|
207
|
+
if (tool.name === "git" && action) {
|
|
208
|
+
return `git-${action}`;
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
new Set(["npm", "pnpm", "yarn", "bun"]).has(tool.name)
|
|
212
|
+
&& action
|
|
213
|
+
) {
|
|
214
|
+
return `${tool.name}-${action}`;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function forbiddenLegacyVerificationSideEffect(executable, argv) {
|
|
220
|
+
const tool = effectiveTool(executable, argv);
|
|
221
|
+
if (FORBIDDEN_EXTERNAL_TOOLS.has(tool.name)) return tool.name;
|
|
222
|
+
const lowered = tool.argv.map((argument) => argument.toLowerCase());
|
|
223
|
+
const gitAction = lowered.find((argument) =>
|
|
224
|
+
LEGACY_FORBIDDEN_GIT_ACTIONS.has(argument));
|
|
225
|
+
if (tool.name === "git" && gitAction) return `git-${gitAction}`;
|
|
226
|
+
const packageAction = lowered.find((argument) =>
|
|
227
|
+
LEGACY_FORBIDDEN_PACKAGE_ACTIONS.has(argument));
|
|
228
|
+
if (PACKAGE_TOOLS.has(tool.name) && packageAction) {
|
|
229
|
+
return `${tool.name}-${packageAction}`;
|
|
230
|
+
}
|
|
231
|
+
const deploymentAction = lowered.find((argument) =>
|
|
232
|
+
argument === "deploy" || argument === "publish");
|
|
233
|
+
if (
|
|
234
|
+
deploymentAction
|
|
235
|
+
&& (PACKAGE_TOOLS.has(tool.name) || KNOWN_DEPLOYMENT_TOOLS.has(tool.name))
|
|
236
|
+
) {
|
|
237
|
+
return `${tool.name}-${deploymentAction}`;
|
|
238
|
+
}
|
|
239
|
+
if (tool.name === "deploy" || tool.name === "publish") return tool.name;
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function safeNodeSyntaxCheck({ workspaceRoot, cwd, executable, argv }) {
|
|
244
|
+
if (
|
|
245
|
+
executable !== realpathSync(process.execPath)
|
|
246
|
+
|| argv.length !== 2
|
|
247
|
+
|| argv[0] !== "--check"
|
|
248
|
+
) return false;
|
|
249
|
+
safeRelativePath(argv[1], "Formal Node syntax-check target");
|
|
250
|
+
const cwdRoot = path.resolve(workspaceRoot, cwd);
|
|
251
|
+
const targetPath = path.resolve(cwdRoot, argv[1]);
|
|
252
|
+
if (!withinRoot(workspaceRoot, targetPath)) return false;
|
|
253
|
+
const stat = lstatSync(targetPath);
|
|
254
|
+
return !stat.isSymbolicLink()
|
|
255
|
+
&& stat.isFile()
|
|
256
|
+
&& realpathSync(targetPath) === targetPath;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function validateFormalVerificationCommandV1({
|
|
260
|
+
workspaceRoot,
|
|
261
|
+
cwd = ".",
|
|
262
|
+
commandArgv,
|
|
263
|
+
}) {
|
|
264
|
+
safeRelativePath(cwd, "Formal check cwd", { allowDot: true });
|
|
265
|
+
if (
|
|
266
|
+
!Array.isArray(commandArgv)
|
|
267
|
+
|| commandArgv.length === 0
|
|
268
|
+
|| commandArgv.some((argument) => typeof argument !== "string")
|
|
269
|
+
) {
|
|
270
|
+
throw new Error("Formal check requires one exact command.");
|
|
271
|
+
}
|
|
272
|
+
const root = realpathSync(workspaceRoot);
|
|
273
|
+
const [rawExecutable, ...argv] = commandArgv;
|
|
274
|
+
const executable = regularExecutable(rawExecutable);
|
|
275
|
+
const forbidden = forbiddenFormalCommand(executable, argv);
|
|
276
|
+
if (forbidden) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Formal verification-only policy forbids external side-effect command: ${forbidden}.`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (safeNodeSyntaxCheck({
|
|
282
|
+
workspaceRoot: root,
|
|
283
|
+
cwd,
|
|
284
|
+
executable,
|
|
285
|
+
argv,
|
|
286
|
+
})) {
|
|
287
|
+
return {
|
|
288
|
+
schemaVersion: "OwlCodaRunKitFormalVerificationCommandPolicyV1",
|
|
289
|
+
mode: "built_in_safe_verification",
|
|
290
|
+
riskClassification: "built_in_read_only_verification",
|
|
291
|
+
profileCommandIds: [],
|
|
292
|
+
executable,
|
|
293
|
+
argv,
|
|
294
|
+
authorizationGranted: false,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
throw new Error(
|
|
298
|
+
"Formal accepted evidence only admits built-in safe verification; "
|
|
299
|
+
+ "profile, package, wrapper, and custom commands lack an enforced "
|
|
300
|
+
+ "workspace-write and network sandbox.",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function validateLegacyVerificationSideEffectsV1({ commandArgv }) {
|
|
305
|
+
if (
|
|
306
|
+
!Array.isArray(commandArgv)
|
|
307
|
+
|| commandArgv.length === 0
|
|
308
|
+
|| commandArgv.some((argument) => typeof argument !== "string")
|
|
309
|
+
) {
|
|
310
|
+
throw new Error("Legacy verification requires one exact command.");
|
|
311
|
+
}
|
|
312
|
+
const [executable, ...argv] = commandArgv;
|
|
313
|
+
const forbidden = forbiddenLegacyVerificationSideEffect(executable, argv);
|
|
314
|
+
if (forbidden) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`Legacy verification command forbids external side effect: ${forbidden}.`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
schemaVersion: "OwlCodaRunKitLegacyVerificationSideEffectPolicyV1",
|
|
321
|
+
mode: "known_high_risk_side_effect_deny",
|
|
322
|
+
forbidden: false,
|
|
323
|
+
authorizationGranted: false,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function readChecks(executionRoot) {
|
|
328
|
+
const root = checkRoot(executionRoot);
|
|
329
|
+
if (!existsSync(root)) return [];
|
|
330
|
+
const stat = lstatSync(root);
|
|
331
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
332
|
+
throw new Error("Formal checks root must be a regular directory.");
|
|
333
|
+
}
|
|
334
|
+
return readdirSync(root, { withFileTypes: true })
|
|
335
|
+
.filter((entry) => entry.name.endsWith(".json"))
|
|
336
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
337
|
+
.map((entry) => {
|
|
338
|
+
if (entry.isSymbolicLink() || !entry.isFile()) {
|
|
339
|
+
throw new Error(`Formal check must be a regular file: ${entry.name}`);
|
|
340
|
+
}
|
|
341
|
+
const value = readJson(path.join(root, entry.name));
|
|
342
|
+
if (
|
|
343
|
+
value.schemaVersion !== "OwlCodaRunKitFormalCheckV1"
|
|
344
|
+
|| !Number.isSafeInteger(value.sequence)
|
|
345
|
+
|| value.sequence < 1
|
|
346
|
+
) {
|
|
347
|
+
throw new Error(`Formal check contract is invalid: ${entry.name}`);
|
|
348
|
+
}
|
|
349
|
+
return value;
|
|
350
|
+
})
|
|
351
|
+
.sort((left, right) => left.sequence - right.sequence);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function resumeOrFreezeCandidate({
|
|
355
|
+
workspaceRoot,
|
|
356
|
+
executionRoot,
|
|
357
|
+
runId,
|
|
358
|
+
workItemId,
|
|
359
|
+
checkId,
|
|
360
|
+
}) {
|
|
361
|
+
const candidatePath = path.join(
|
|
362
|
+
executionRoot,
|
|
363
|
+
"source-candidates",
|
|
364
|
+
`${checkId}.json`,
|
|
365
|
+
);
|
|
366
|
+
if (!existsSync(candidatePath)) {
|
|
367
|
+
return {
|
|
368
|
+
candidate: freezeSourceCandidateWithinControlTransactionV2({
|
|
369
|
+
workspaceRoot,
|
|
370
|
+
runId,
|
|
371
|
+
workItemId,
|
|
372
|
+
candidateId: checkId,
|
|
373
|
+
}),
|
|
374
|
+
resumed: false,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const candidateDocument = readRegularJson(
|
|
378
|
+
candidatePath,
|
|
379
|
+
"Formal source candidate",
|
|
380
|
+
);
|
|
381
|
+
const candidateRelativePath = relativeToWorkspace(
|
|
382
|
+
workspaceRoot,
|
|
383
|
+
candidatePath,
|
|
384
|
+
);
|
|
385
|
+
const gate = verifySourceCandidateV2({
|
|
386
|
+
workspaceRoot,
|
|
387
|
+
candidatePath: candidateRelativePath,
|
|
388
|
+
});
|
|
389
|
+
if (
|
|
390
|
+
gate.status !== "valid"
|
|
391
|
+
|| candidateDocument.runId !== runId
|
|
392
|
+
|| candidateDocument.candidateId !== checkId
|
|
393
|
+
|| candidateDocument.discovery?.fromLease !== workItemId
|
|
394
|
+
) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Existing Formal source candidate differs from the resumed command: ${checkId}`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
candidate: {
|
|
401
|
+
status: "source_candidate_frozen",
|
|
402
|
+
exitCode: 0,
|
|
403
|
+
runId,
|
|
404
|
+
candidateId: checkId,
|
|
405
|
+
candidatePath: candidateRelativePath,
|
|
406
|
+
candidateSha256: candidateDocument.candidateSha256,
|
|
407
|
+
sourceFingerprint: candidateDocument.sourceFingerprint.sha256,
|
|
408
|
+
manifestEntryCount: candidateDocument.sourceManifest.entries.length,
|
|
409
|
+
payloadSha256: candidateDocument.payload.sha256,
|
|
410
|
+
ownedPaths: [...candidateDocument.sourceManifest.ownedPaths],
|
|
411
|
+
authorizationGranted: false,
|
|
412
|
+
},
|
|
413
|
+
resumed: true,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function resumeSnapshot({
|
|
418
|
+
workspaceRoot,
|
|
419
|
+
executionRoot,
|
|
420
|
+
runId,
|
|
421
|
+
request,
|
|
422
|
+
candidateDocument,
|
|
423
|
+
}) {
|
|
424
|
+
const snapshotPath = path.join(
|
|
425
|
+
executionRoot,
|
|
426
|
+
"snapshots",
|
|
427
|
+
`${request.snapshotId}.json`,
|
|
428
|
+
);
|
|
429
|
+
const evidenceRoot = path.join(executionRoot, "snapshot-evidence");
|
|
430
|
+
const stdoutPath = path.join(evidenceRoot, `${request.snapshotId}.stdout`);
|
|
431
|
+
const stderrPath = path.join(evidenceRoot, `${request.snapshotId}.stderr`);
|
|
432
|
+
if (!existsSync(snapshotPath)) {
|
|
433
|
+
if (existsSync(stdoutPath) || existsSync(stderrPath)) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
"Incomplete Formal snapshot evidence requires a new check id.",
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
snapshot: runSnapshot({ workspaceRoot, runId, request }),
|
|
440
|
+
resumed: false,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const snapshot = readRegularJson(snapshotPath, "Formal snapshot");
|
|
444
|
+
const expectedFiles = Object.fromEntries(
|
|
445
|
+
candidateDocument.sourceManifest.entries
|
|
446
|
+
.filter(entry => entry.operation !== "deleted")
|
|
447
|
+
.map(entry => [entry.path, entry.sha256])
|
|
448
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
449
|
+
);
|
|
450
|
+
const expectedInputs = Object.entries(expectedFiles)
|
|
451
|
+
.map(([id, hash]) => ({ id, sha256: hash }));
|
|
452
|
+
const evidence = verifySnapshotEvidence({
|
|
453
|
+
workspaceRoot,
|
|
454
|
+
runId,
|
|
455
|
+
snapshot,
|
|
456
|
+
});
|
|
457
|
+
if (
|
|
458
|
+
!evidence.valid
|
|
459
|
+
|| snapshot.snapshotId !== request.snapshotId
|
|
460
|
+
|| snapshot.targetRoot !== request.targetRoot
|
|
461
|
+
|| snapshot.verificationContextFingerprint
|
|
462
|
+
!== verificationContextFingerprint(request.verificationContext)
|
|
463
|
+
|| snapshot.repositoryBefore?.head !== candidateDocument.baseline.head
|
|
464
|
+
|| snapshot.repositoryAfter?.head !== candidateDocument.baseline.head
|
|
465
|
+
|| canonicalJson(snapshot.repositoryBefore?.selectedFiles)
|
|
466
|
+
!== canonicalJson(expectedFiles)
|
|
467
|
+
|| canonicalJson(snapshot.repositoryAfter?.selectedFiles)
|
|
468
|
+
!== canonicalJson(expectedFiles)
|
|
469
|
+
|| snapshot.command?.evidence?.cwd !== request.cwd
|
|
470
|
+
|| canonicalJson(snapshot.command?.evidence?.argv)
|
|
471
|
+
!== canonicalJson([request.executable, ...request.argv])
|
|
472
|
+
|| canonicalJson(snapshot.command?.evidence?.materialInputs)
|
|
473
|
+
!== canonicalJson(expectedInputs)
|
|
474
|
+
) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`Existing Formal snapshot differs from the resumed command: ${request.snapshotId}`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
snapshot: {
|
|
481
|
+
status: snapshot.status,
|
|
482
|
+
exitCode: snapshot.status === "snapshot_passed" ? 0 : 2,
|
|
483
|
+
runId,
|
|
484
|
+
snapshotPath: relativeToWorkspace(workspaceRoot, snapshotPath),
|
|
485
|
+
authorizationGranted: false,
|
|
486
|
+
},
|
|
487
|
+
resumed: true,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function recordFormalCheckWithinControlTransaction({
|
|
492
|
+
workspaceRoot,
|
|
493
|
+
runId,
|
|
494
|
+
workItemId,
|
|
495
|
+
checkId,
|
|
496
|
+
cwd = ".",
|
|
497
|
+
commandArgv,
|
|
498
|
+
hooks = {},
|
|
499
|
+
}) {
|
|
500
|
+
safeIdentifier(checkId, "checkId");
|
|
501
|
+
safeRelativePath(cwd, "Formal check cwd", { allowDot: true });
|
|
502
|
+
if (!Array.isArray(commandArgv) || commandArgv.length === 0) {
|
|
503
|
+
throw new Error("Formal check requires one exact command.");
|
|
504
|
+
}
|
|
505
|
+
const root = realpathSync(workspaceRoot);
|
|
506
|
+
const { executionRoot, pinGate } = loadActiveExecution(root, runId);
|
|
507
|
+
if (pinGate.status !== "valid") return { ...pinGate, authorizationGranted: false };
|
|
508
|
+
const verificationCommandPolicy = validateFormalVerificationCommandV1({
|
|
509
|
+
workspaceRoot: root,
|
|
510
|
+
cwd,
|
|
511
|
+
commandArgv,
|
|
512
|
+
});
|
|
513
|
+
const receiptPath = path.join(checkRoot(executionRoot), `${checkId}.json`);
|
|
514
|
+
const candidateResult = resumeOrFreezeCandidate({
|
|
515
|
+
workspaceRoot: root,
|
|
516
|
+
executionRoot,
|
|
517
|
+
runId,
|
|
518
|
+
workItemId,
|
|
519
|
+
checkId,
|
|
520
|
+
});
|
|
521
|
+
const candidate = candidateResult.candidate;
|
|
522
|
+
if (candidate.status !== "source_candidate_frozen") return candidate;
|
|
523
|
+
hooks.afterCandidatePersist?.();
|
|
524
|
+
const candidateDocument = readJson(path.join(root, candidate.candidatePath));
|
|
525
|
+
const context = derivedVerificationContext(root);
|
|
526
|
+
const { executable, argv } = verificationCommandPolicy;
|
|
527
|
+
const request = {
|
|
528
|
+
schemaVersion: "OwlCodaRunKitSnapshotRequestV1",
|
|
529
|
+
snapshotId: `${checkId}-snapshot`,
|
|
530
|
+
mode: "project",
|
|
531
|
+
targetRoot: root,
|
|
532
|
+
cwd,
|
|
533
|
+
executable,
|
|
534
|
+
argv,
|
|
535
|
+
launcherVersion: "owlrunkit-formal-check-v2",
|
|
536
|
+
toolVersions: structuredClone(context.toolchains),
|
|
537
|
+
selectedPaths: candidateDocument.sourceManifest.entries
|
|
538
|
+
.filter((entry) => entry.operation !== "deleted")
|
|
539
|
+
.map((entry) => entry.path)
|
|
540
|
+
.sort(),
|
|
541
|
+
statusMode: "porcelain-v1-z-untracked-all-runkit-excluded",
|
|
542
|
+
verificationContext: context,
|
|
543
|
+
};
|
|
544
|
+
const requestPath = path.join(
|
|
545
|
+
executionRoot,
|
|
546
|
+
"formal-check-requests",
|
|
547
|
+
`${checkId}.json`,
|
|
548
|
+
);
|
|
549
|
+
const requestResult = writeOrResumeExactJson(
|
|
550
|
+
requestPath,
|
|
551
|
+
request,
|
|
552
|
+
"Formal check request",
|
|
553
|
+
);
|
|
554
|
+
hooks.afterRequestPersist?.();
|
|
555
|
+
const snapshotResult = resumeSnapshot({
|
|
556
|
+
workspaceRoot: root,
|
|
557
|
+
executionRoot,
|
|
558
|
+
runId,
|
|
559
|
+
request: requestResult.value,
|
|
560
|
+
candidateDocument,
|
|
561
|
+
});
|
|
562
|
+
const snapshot = snapshotResult.snapshot;
|
|
563
|
+
hooks.afterSnapshotPersist?.();
|
|
564
|
+
const savedSnapshot = readJson(path.join(root, snapshot.snapshotPath));
|
|
565
|
+
const existingReceipt = existsSync(receiptPath)
|
|
566
|
+
? readRegularJson(receiptPath, "Formal check receipt")
|
|
567
|
+
: null;
|
|
568
|
+
const sequence = existingReceipt?.sequence
|
|
569
|
+
?? readChecks(executionRoot).length + 1;
|
|
570
|
+
const status = snapshot.status === "snapshot_passed"
|
|
571
|
+
? "formal_check_passed"
|
|
572
|
+
: "formal_check_failed";
|
|
573
|
+
const receipt = {
|
|
574
|
+
schemaVersion: "OwlCodaRunKitFormalCheckV1",
|
|
575
|
+
runId,
|
|
576
|
+
checkId,
|
|
577
|
+
sequence,
|
|
578
|
+
status,
|
|
579
|
+
candidatePath: candidate.candidatePath,
|
|
580
|
+
candidateSha256: candidate.candidateSha256,
|
|
581
|
+
payloadSha256: candidate.payloadSha256,
|
|
582
|
+
sourceFingerprint: candidate.sourceFingerprint,
|
|
583
|
+
verificationContext: context,
|
|
584
|
+
verificationContextFingerprint: verificationContextFingerprint(context),
|
|
585
|
+
snapshotRequestPath: relativeToWorkspace(root, requestPath),
|
|
586
|
+
snapshotPath: snapshot.snapshotPath,
|
|
587
|
+
commandExitCode: savedSnapshot.command.exitCode,
|
|
588
|
+
verificationCommandPolicy: {
|
|
589
|
+
schemaVersion: verificationCommandPolicy.schemaVersion,
|
|
590
|
+
mode: verificationCommandPolicy.mode,
|
|
591
|
+
riskClassification: verificationCommandPolicy.riskClassification,
|
|
592
|
+
profileCommandIds: verificationCommandPolicy.profileCommandIds,
|
|
593
|
+
authorizationGranted: false,
|
|
594
|
+
},
|
|
595
|
+
finalized: false,
|
|
596
|
+
authorizationGranted: false,
|
|
597
|
+
};
|
|
598
|
+
const receiptResult = writeOrResumeExactJson(
|
|
599
|
+
receiptPath,
|
|
600
|
+
receipt,
|
|
601
|
+
"Formal check receipt",
|
|
602
|
+
);
|
|
603
|
+
hooks.afterReceiptPersist?.();
|
|
604
|
+
return {
|
|
605
|
+
...receiptResult.value,
|
|
606
|
+
exitCode: status === "formal_check_passed" ? 0 : 2,
|
|
607
|
+
checkReceiptPath: relativeToWorkspace(root, receiptPath),
|
|
608
|
+
resumed: candidateResult.resumed
|
|
609
|
+
|| requestResult.resumed
|
|
610
|
+
|| snapshotResult.resumed
|
|
611
|
+
|| receiptResult.resumed,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function recordFormalCheckV1(options) {
|
|
616
|
+
const workspaceRoot = realpathSync(options.workspaceRoot);
|
|
617
|
+
return withControlTransaction(
|
|
618
|
+
workspaceRoot,
|
|
619
|
+
() => recordFormalCheckWithinControlTransaction({
|
|
620
|
+
...options,
|
|
621
|
+
workspaceRoot,
|
|
622
|
+
}),
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function finalizeFormalChecksWithinControlTransaction({
|
|
627
|
+
workspaceRoot,
|
|
628
|
+
runId,
|
|
629
|
+
finalizeId,
|
|
630
|
+
hooks = {},
|
|
631
|
+
}) {
|
|
632
|
+
safeIdentifier(finalizeId, "finalizeId");
|
|
633
|
+
const root = realpathSync(workspaceRoot);
|
|
634
|
+
const { executionRoot, pinGate } = loadActiveExecution(root, runId);
|
|
635
|
+
if (pinGate.status !== "valid") return { ...pinGate, authorizationGranted: false };
|
|
636
|
+
const checks = readChecks(executionRoot);
|
|
637
|
+
if (checks.length === 0) throw new Error("Formal finish requires at least one check.");
|
|
638
|
+
const latest = checks.at(-1);
|
|
639
|
+
const currentCandidate = verifySourceCandidateV2({
|
|
640
|
+
workspaceRoot: root,
|
|
641
|
+
candidatePath: latest.candidatePath,
|
|
642
|
+
});
|
|
643
|
+
if (currentCandidate.status !== "valid") {
|
|
644
|
+
throw new Error("Formal finish requires the latest source candidate to remain current.");
|
|
645
|
+
}
|
|
646
|
+
const selected = checks.filter((check) => (
|
|
647
|
+
check.status === "formal_check_passed"
|
|
648
|
+
&& check.sourceFingerprint === latest.sourceFingerprint
|
|
649
|
+
&& check.verificationContextFingerprint === latest.verificationContextFingerprint
|
|
650
|
+
));
|
|
651
|
+
if (latest.status !== "formal_check_passed" || selected.length === 0) {
|
|
652
|
+
throw new Error("Formal finish requires the latest source to have a passed check.");
|
|
653
|
+
}
|
|
654
|
+
const request = {
|
|
655
|
+
schemaVersion: "OwlCodaRunKitFinalizeRequestV1",
|
|
656
|
+
receiptId: `${finalizeId}-receipt`,
|
|
657
|
+
sourceCandidatePath: latest.candidatePath,
|
|
658
|
+
verificationContext: structuredClone(latest.verificationContext),
|
|
659
|
+
snapshotPaths: selected.map((check) => check.snapshotPath),
|
|
660
|
+
};
|
|
661
|
+
const requestPath = path.join(
|
|
662
|
+
executionRoot,
|
|
663
|
+
"formal-finalize-requests",
|
|
664
|
+
`${finalizeId}.json`,
|
|
665
|
+
);
|
|
666
|
+
writeOrResumeExactJson(
|
|
667
|
+
requestPath,
|
|
668
|
+
request,
|
|
669
|
+
"Formal finalize request",
|
|
670
|
+
);
|
|
671
|
+
const finalized = runFinalize({
|
|
672
|
+
workspaceRoot: root,
|
|
673
|
+
runId,
|
|
674
|
+
request,
|
|
675
|
+
hooks,
|
|
676
|
+
});
|
|
677
|
+
return {
|
|
678
|
+
...finalized,
|
|
679
|
+
finalizeRequestPath: relativeToWorkspace(root, requestPath),
|
|
680
|
+
snapshotCount: selected.length,
|
|
681
|
+
staleCheckCount: checks.length - selected.length,
|
|
682
|
+
sourceFingerprint: latest.sourceFingerprint,
|
|
683
|
+
sourceCandidatePath: latest.candidatePath,
|
|
684
|
+
authorizationGranted: false,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export function finalizeFormalChecksV1(options) {
|
|
689
|
+
const workspaceRoot = realpathSync(options.workspaceRoot);
|
|
690
|
+
return withControlTransaction(
|
|
691
|
+
workspaceRoot,
|
|
692
|
+
() => finalizeFormalChecksWithinControlTransaction({
|
|
693
|
+
...options,
|
|
694
|
+
workspaceRoot,
|
|
695
|
+
}),
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function resumeCompletedFormalFinishV1({
|
|
700
|
+
workspaceRoot,
|
|
701
|
+
runId,
|
|
702
|
+
finalizeId,
|
|
703
|
+
decision,
|
|
704
|
+
}) {
|
|
705
|
+
safeIdentifier(finalizeId, "finalizeId");
|
|
706
|
+
const root = realpathSync(workspaceRoot);
|
|
707
|
+
const executionRoot = path.join(
|
|
708
|
+
root,
|
|
709
|
+
".owlcoda",
|
|
710
|
+
"runkit",
|
|
711
|
+
"executions",
|
|
712
|
+
runId,
|
|
713
|
+
);
|
|
714
|
+
const closeoutPath = path.join(executionRoot, "closeout-receipt.json");
|
|
715
|
+
if (!existsSync(closeoutPath)) return null;
|
|
716
|
+
const closeout = readRegularJson(closeoutPath, "Formal closeout receipt");
|
|
717
|
+
const gate = validateCoreArtifact(closeout.artifact);
|
|
718
|
+
const payload = closeout.artifact?.payload;
|
|
719
|
+
if (
|
|
720
|
+
!gate.valid
|
|
721
|
+
|| closeout.acceptanceSha256 !== gate.acceptanceSha256
|
|
722
|
+
|| closeout.artifactSha256 !== gate.artifactSha256
|
|
723
|
+
|| payload?.runId !== runId
|
|
724
|
+
|| payload?.decision !== decision
|
|
725
|
+
|| payload?.authorizationGranted !== false
|
|
726
|
+
|| (decision === "accepted"
|
|
727
|
+
&& acceptedCloseoutVerificationIssues(closeout.artifact).length > 0)
|
|
728
|
+
) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
"Existing closeout does not match the resumed Formal finish command.",
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
if (decision !== "accepted") {
|
|
734
|
+
return {
|
|
735
|
+
status: "formal_finished",
|
|
736
|
+
exitCode: 0,
|
|
737
|
+
runId,
|
|
738
|
+
decision,
|
|
739
|
+
snapshotCount: 0,
|
|
740
|
+
staleCheckCount: 0,
|
|
741
|
+
sourceFingerprint: null,
|
|
742
|
+
activeReceiptSha256: null,
|
|
743
|
+
closeoutArtifactSha256: closeout.artifactSha256,
|
|
744
|
+
releasedLeaseIds: [],
|
|
745
|
+
resumed: true,
|
|
746
|
+
authorizationGranted: false,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const requestPath = path.join(
|
|
750
|
+
executionRoot,
|
|
751
|
+
"formal-finalize-requests",
|
|
752
|
+
`${finalizeId}.json`,
|
|
753
|
+
);
|
|
754
|
+
const request = readRegularJson(
|
|
755
|
+
requestPath,
|
|
756
|
+
"Formal finalize request",
|
|
757
|
+
);
|
|
758
|
+
if (
|
|
759
|
+
request.schemaVersion !== "OwlCodaRunKitFinalizeRequestV1"
|
|
760
|
+
|| request.receiptId !== `${finalizeId}-receipt`
|
|
761
|
+
|| typeof request.sourceCandidatePath !== "string"
|
|
762
|
+
|| request.deliveryPacketPath !== undefined
|
|
763
|
+
) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
"Existing closeout does not match the resumed Formal finalize request.",
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
const lineagePath = path.join(
|
|
769
|
+
executionRoot,
|
|
770
|
+
"verification-receipts",
|
|
771
|
+
"receipt-lineage.json",
|
|
772
|
+
);
|
|
773
|
+
const lineage = readRegularJson(
|
|
774
|
+
lineagePath,
|
|
775
|
+
"Formal verification receipt lineage",
|
|
776
|
+
);
|
|
777
|
+
const validated = validateReceiptLineage(lineage);
|
|
778
|
+
const active = validated.active;
|
|
779
|
+
if (
|
|
780
|
+
!validated.valid
|
|
781
|
+
|| active?.receipt?.status !== "passed"
|
|
782
|
+
|| active.receipt.runId !== runId
|
|
783
|
+
|| active.receipt.receiptId !== request.receiptId
|
|
784
|
+
|| active.receipt.sourceArtifact?.kind !== "source_candidate_v2"
|
|
785
|
+
|| active.receipt.sourceArtifact.path !== request.sourceCandidatePath
|
|
786
|
+
|| payload.verification.activeReceiptSha256 !== active.receiptSha256
|
|
787
|
+
|| canonicalJson(payload.verification.sourceArtifact)
|
|
788
|
+
!== canonicalJson(active.receipt.sourceArtifact)
|
|
789
|
+
) {
|
|
790
|
+
throw new Error(
|
|
791
|
+
"Existing closeout does not match the resumed Formal verification lineage.",
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
const sourceArtifactPath = safeRelativePath(
|
|
795
|
+
active.receipt.sourceArtifact.path,
|
|
796
|
+
"Formal sourceArtifact path",
|
|
797
|
+
);
|
|
798
|
+
const expectedSourceRoot =
|
|
799
|
+
`.owlcoda/runkit/executions/${runId}/source-candidates/`;
|
|
800
|
+
if (!sourceArtifactPath.startsWith(expectedSourceRoot)) {
|
|
801
|
+
throw new Error(
|
|
802
|
+
"Existing closeout Formal sourceArtifact is outside its execution.",
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
const sourceArtifactAbsolutePath = path.join(root, sourceArtifactPath);
|
|
806
|
+
readRegularJson(
|
|
807
|
+
sourceArtifactAbsolutePath,
|
|
808
|
+
"Formal sourceArtifact",
|
|
809
|
+
);
|
|
810
|
+
if (
|
|
811
|
+
sha256(readFileSync(sourceArtifactAbsolutePath))
|
|
812
|
+
!== active.receipt.sourceArtifact.sha256
|
|
813
|
+
) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
"Existing closeout Formal sourceArtifact bytes changed.",
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const checks = readChecks(executionRoot);
|
|
819
|
+
const selected = checks.filter((check) => (
|
|
820
|
+
check.status === "formal_check_passed"
|
|
821
|
+
&& check.sourceFingerprint === active.receipt.sourceFingerprint
|
|
822
|
+
&& check.verificationContextFingerprint
|
|
823
|
+
=== active.receipt.verificationContextFingerprint
|
|
824
|
+
));
|
|
825
|
+
if (
|
|
826
|
+
selected.length === 0
|
|
827
|
+
|| canonicalJson(selected.map(check => check.snapshotPath))
|
|
828
|
+
!== canonicalJson(request.snapshotPaths)
|
|
829
|
+
) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
"Existing closeout does not match the resumed Formal check set.",
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
status: "formal_finished",
|
|
836
|
+
exitCode: 0,
|
|
837
|
+
runId,
|
|
838
|
+
decision,
|
|
839
|
+
snapshotCount: selected.length,
|
|
840
|
+
staleCheckCount: checks.length - selected.length,
|
|
841
|
+
sourceFingerprint: active.receipt.sourceFingerprint,
|
|
842
|
+
activeReceiptSha256: active.receiptSha256,
|
|
843
|
+
closeoutArtifactSha256: closeout.artifactSha256,
|
|
844
|
+
releasedLeaseIds: [
|
|
845
|
+
...payload.verification.releasedLeaseIds,
|
|
846
|
+
],
|
|
847
|
+
resumed: true,
|
|
848
|
+
authorizationGranted: false,
|
|
849
|
+
};
|
|
850
|
+
}
|