owlrunkit 0.19.1 → 0.20.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 (42) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +107 -3
  3. package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +11 -5
  4. package/docs/architecture/OWLCODA_RUNKIT_0192_FORMAL_USABILITY_PRODUCT_DECISION_20260813.md +141 -0
  5. package/docs/architecture/OWLCODA_RUNKIT_020_TEAM_DELIVERY_PHASE1_PRODUCT_DECISION_20260813.md +296 -0
  6. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +74 -9
  7. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  8. package/package.json +1 -1
  9. package/packages/attest/distribution-manifest-v1.json +10 -0
  10. package/packages/attest/src/quick.mjs +42 -4
  11. package/registry-release-policy-v1.json +5 -5
  12. package/sbom.spdx.json +5 -5
  13. package/scripts/runkit-contract/core-contract.mjs +42 -2
  14. package/scripts/runkit-contract/efficiency-metrics.mjs +9 -7
  15. package/scripts/runkit-contract/formal-preflight.mjs +352 -0
  16. package/scripts/runkit-contract/formal-supplemental-evidence.mjs +713 -0
  17. package/scripts/runkit-contract/formal-workflow.mjs +529 -59
  18. package/scripts/runkit-contract/onboarding-doctor.mjs +32 -13
  19. package/scripts/runkit-contract/project-cli-resolver.mjs +33 -3
  20. package/scripts/runkit-contract/project-external-delivery.mjs +16 -9
  21. package/scripts/runkit-contract/quick-attest.mjs +24 -0
  22. package/scripts/runkit-contract/runkit-cli.mjs +445 -52
  23. package/scripts/runkit-contract/schemas/candidate-transfer-packet-v1.schema.json +98 -0
  24. package/scripts/runkit-contract/schemas/formal-check-v2.schema.json +231 -0
  25. package/scripts/runkit-contract/schemas/formal-envelope-preflight-v1.schema.json +212 -0
  26. package/scripts/runkit-contract/schemas/formal-evidence-summary-v1.schema.json +114 -0
  27. package/scripts/runkit-contract/schemas/formal-supplemental-evidence-v1.schema.json +103 -0
  28. package/scripts/runkit-contract/schemas/team-acceptance-packet-v2.schema.json +109 -0
  29. package/scripts/runkit-contract/schemas/team-delivery-status-v1.schema.json +72 -0
  30. package/scripts/runkit-contract/schemas/team-packet-validation-v1.schema.json +33 -0
  31. package/scripts/runkit-contract/schemas/team-project-event-v5.schema.json +89 -0
  32. package/scripts/runkit-contract/schemas/team-project-status-v3.schema.json +173 -0
  33. package/scripts/runkit-contract/schemas/team-recommendation-v1.schema.json +121 -0
  34. package/scripts/runkit-contract/schemas/team-task-packet-v2.schema.json +228 -0
  35. package/scripts/runkit-contract/snapshot.mjs +125 -5
  36. package/scripts/runkit-contract/source-candidate.mjs +271 -69
  37. package/scripts/runkit-contract/sync-release-identity.mjs +39 -4
  38. package/scripts/runkit-contract/team-delivery.mjs +906 -0
  39. package/scripts/runkit-contract/team-project-successor.mjs +7 -4
  40. package/scripts/runkit-contract/team-project.mjs +440 -28
  41. package/scripts/runkit-contract/usage-mode.mjs +9 -3
  42. package/scripts/runkit-contract/verification-envelope.mjs +425 -37
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  import { parseProjectControlState } from "./project-control-parser.mjs";
15
15
 
16
16
  export const CONTRACT_VERSION = "0.2";
17
- export const CORE_VERSION = "0.19.1";
17
+ export const CORE_VERSION = "0.20.0";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -30,6 +30,8 @@ const CORE_FILES = [
30
30
  "fleet-discovery.mjs",
31
31
  "finalize.mjs",
32
32
  "formal-workflow.mjs",
33
+ "formal-preflight.mjs",
34
+ "formal-supplemental-evidence.mjs",
33
35
  "human-status.mjs",
34
36
  "inspect-presentation.mjs",
35
37
  "install-codex-skill.mjs",
@@ -76,6 +78,7 @@ const CORE_FILES = [
76
78
  "sync-release-identity.mjs",
77
79
  "team-project-successor.mjs",
78
80
  "team-project.mjs",
81
+ "team-delivery.mjs",
79
82
  "usage-mode.mjs",
80
83
  "verification-envelope.mjs",
81
84
  "verification-envelope-check.mjs",
@@ -104,6 +107,10 @@ const CORE_FILES = [
104
107
  "schemas/fleet-discovery-v1.schema.json",
105
108
  "schemas/fleet-manifest-v1.schema.json",
106
109
  "schemas/fleet-root-registry-v1.schema.json",
110
+ "schemas/formal-check-v2.schema.json",
111
+ "schemas/formal-envelope-preflight-v1.schema.json",
112
+ "schemas/formal-evidence-summary-v1.schema.json",
113
+ "schemas/formal-supplemental-evidence-v1.schema.json",
107
114
  "schemas/human-status-v1.schema.json",
108
115
  "schemas/onboarding-bootstrap-plan-v1.schema.json",
109
116
  "schemas/onboarding-bootstrap-receipt-v1.schema.json",
@@ -140,6 +147,12 @@ const CORE_FILES = [
140
147
  "schemas/runtime-transition-receipt-v1.schema.json",
141
148
  "schemas/snapshot-v1.schema.json",
142
149
  "schemas/source-candidate-v2.schema.json",
150
+ "schemas/candidate-transfer-packet-v1.schema.json",
151
+ "schemas/team-acceptance-packet-v2.schema.json",
152
+ "schemas/team-delivery-status-v1.schema.json",
153
+ "schemas/team-packet-validation-v1.schema.json",
154
+ "schemas/team-recommendation-v1.schema.json",
155
+ "schemas/team-task-packet-v2.schema.json",
143
156
  "schemas/visual-smoke-request-v1.schema.json",
144
157
  "schemas/visual-smoke-result-v1.schema.json",
145
158
  "schemas/verification-context-v1.schema.json",
@@ -148,9 +161,11 @@ const CORE_FILES = [
148
161
  "schemas/team-project-event-v2.schema.json",
149
162
  "schemas/team-project-event-v3.schema.json",
150
163
  "schemas/team-project-event-v4.schema.json",
164
+ "schemas/team-project-event-v5.schema.json",
151
165
  "schemas/team-project-successor-receipt-v1.schema.json",
152
166
  "schemas/team-project-status-v1.schema.json",
153
167
  "schemas/team-project-status-v2.schema.json",
168
+ "schemas/team-project-status-v3.schema.json",
154
169
  "schemas/usage-mode-recommendation-v1.schema.json",
155
170
  "schemas/usage-mode-recommendation-v2.schema.json",
156
171
  "schemas/verification-envelope-v1.schema.json",
@@ -191,6 +206,7 @@ const CORE_DEPENDENCY_FILES = [
191
206
  "packages/attest/src/formal.mjs",
192
207
  "packages/attest/src/offline-bundle.mjs",
193
208
  "packages/attest/src/quick-receipt-contract.mjs",
209
+ "packages/attest/src/quick.mjs",
194
210
  "packages/attest/src/reference-contract.mjs",
195
211
  "packages/attest/src/workspace-snapshot.mjs",
196
212
  ];
@@ -358,10 +374,34 @@ function coreDirectory() {
358
374
  return resolve(fileURLToPath(new URL(".", import.meta.url)));
359
375
  }
360
376
 
377
+ function dependencyBytesForManifest(projectRoot, name) {
378
+ const bytes = readFileSync(resolve(projectRoot, name));
379
+ if (name !== "packages/attest/src/quick.mjs") return bytes;
380
+ const source = bytes.toString("utf8");
381
+ const escapedVersion = CORE_VERSION.replaceAll(".", "\\.");
382
+ // Normalize only the self-referential release field. The public attester still
383
+ // uses the fixed catalog; release synchronization fills this exact hash later.
384
+ const currentIdentity = new RegExp(
385
+ `(coreVersion:\\s*"${escapedVersion}",\\s*coreManifestSha256:\\s*")`
386
+ + "sha256:[a-f0-9]{64}(\")",
387
+ "gu",
388
+ );
389
+ const matches = [...source.matchAll(currentIdentity)];
390
+ if (matches.length !== 1) {
391
+ throw new Error("Core-bound Quick attester must declare exactly one current release identity.");
392
+ }
393
+ return Buffer.from(source.replace(
394
+ currentIdentity,
395
+ `$1sha256:${"0".repeat(64)}$2`,
396
+ ));
397
+ }
398
+
361
399
  function coreManifestStreamFromRoots(coreRoot, projectRoot) {
362
400
  return [
363
401
  ...CORE_FILES.map((name) => `${name}\tsha256:${sha256(readFileSync(resolve(coreRoot, name)))}\n`),
364
- ...CORE_DEPENDENCY_FILES.map((name) => `${name}\tsha256:${sha256(readFileSync(resolve(projectRoot, name)))}\n`),
402
+ ...CORE_DEPENDENCY_FILES.map((name) => (
403
+ `${name}\tsha256:${sha256(dependencyBytesForManifest(projectRoot, name))}\n`
404
+ )),
365
405
  ].join("");
366
406
  }
367
407
 
@@ -51,12 +51,14 @@ function compareIdentifiers(left, right) {
51
51
 
52
52
  function validateTimestamp(value) {
53
53
  if (typeof value !== "string"
54
- || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
55
- || Number.isNaN(Date.parse(value))
56
- || new Date(value).toISOString() !== value) {
57
- throw new Error("Efficiency event --at must be an ISO-8601 UTC timestamp.");
54
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u.test(value)
55
+ || Number.isNaN(Date.parse(value))) {
56
+ throw new Error(
57
+ "Efficiency event --at must be an ISO-8601 UTC timestamp "
58
+ + "(YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.sssZ).",
59
+ );
58
60
  }
59
- return value;
61
+ return new Date(value).toISOString();
60
62
  }
61
63
 
62
64
  function normalizeEvent(value) {
@@ -74,7 +76,7 @@ function normalizeEvent(value) {
74
76
  throw new Error("Efficiency event and workflow identifiers must not exceed 128 characters.");
75
77
  }
76
78
  if (!EVENT_KINDS.has(value.kind)) throw new Error("Efficiency event kind is unsupported.");
77
- validateTimestamp(value.occurredAt);
79
+ const occurredAt = validateTimestamp(value.occurredAt);
78
80
  if (typeof value.summary !== "string"
79
81
  || value.summary.trim().length === 0
80
82
  || value.summary.length > 2_000) {
@@ -94,7 +96,7 @@ function normalizeEvent(value) {
94
96
  schemaVersion: EVENT_SCHEMA,
95
97
  eventId: value.eventId,
96
98
  kind: value.kind,
97
- occurredAt: value.occurredAt,
99
+ occurredAt,
98
100
  workflowId: value.workflowId,
99
101
  summary: value.summary.trim(),
100
102
  evidenceRefs: [...value.evidenceRefs].sort(compareIdentifiers),
@@ -0,0 +1,352 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ readFileSync,
5
+ realpathSync,
6
+ } from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import { inspectRuntimeIsolationV1 } from "./onboarding-doctor.mjs";
10
+ import {
11
+ resolveVerificationEnvelopeBackendV1,
12
+ validateVerificationEnvelopeV1,
13
+ } from "./verification-envelope.mjs";
14
+
15
+ const PREFLIGHT_SCHEMA = "OwlCodaRunKitFormalEnvelopePreflightV1";
16
+ const NATIVE_TOOLCHAIN_TOOLS = new Set(["esbuild", "vite", "vitest"]);
17
+ const KNOWN_SCRIPT_TOOLS = [
18
+ "biome",
19
+ "eslint",
20
+ "esbuild",
21
+ "jest",
22
+ "markdownlint",
23
+ "markdownlint-cli2",
24
+ "prettier",
25
+ "stylelint",
26
+ "vite",
27
+ "vitest",
28
+ ];
29
+
30
+ function executableName(value) {
31
+ return path.basename(value).toLowerCase().replace(/\.(?:cmd|exe)$/u, "");
32
+ }
33
+
34
+ function effectiveInvocation(phase) {
35
+ const direct = executableName(phase.executable);
36
+ if (
37
+ direct === "node"
38
+ && typeof phase.argv[0] === "string"
39
+ && /^(?:npm-cli|pnpm|yarn)(?:\.[cm]?js)?$/u.test(
40
+ path.basename(phase.argv[0]).toLowerCase(),
41
+ )
42
+ ) {
43
+ const launcher = path.basename(phase.argv[0]).toLowerCase();
44
+ return {
45
+ tool: launcher.startsWith("npm")
46
+ ? "npm"
47
+ : launcher.startsWith("pnpm")
48
+ ? "pnpm"
49
+ : "yarn",
50
+ argv: phase.argv.slice(1),
51
+ };
52
+ }
53
+ return { tool: direct, argv: [...phase.argv] };
54
+ }
55
+
56
+ function packageScriptName(invocation) {
57
+ if (!new Set(["npm", "pnpm", "yarn"]).has(invocation.tool)) return null;
58
+ const actionIndex = invocation.argv.findIndex(value => value === "run" || value === "run-script");
59
+ return actionIndex >= 0 && typeof invocation.argv[actionIndex + 1] === "string"
60
+ ? invocation.argv[actionIndex + 1]
61
+ : null;
62
+ }
63
+
64
+ function readPackageScript(root, cwd, scriptName) {
65
+ if (scriptName === null) return null;
66
+ const manifestPath = path.join(root, cwd, "package.json");
67
+ if (!existsSync(manifestPath)) return null;
68
+ const stat = lstatSync(manifestPath);
69
+ if (
70
+ stat.isSymbolicLink()
71
+ || !stat.isFile()
72
+ || stat.size > 1_048_576
73
+ || realpathSync(manifestPath) !== path.resolve(manifestPath)
74
+ ) return null;
75
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
76
+ return typeof manifest.scripts?.[scriptName] === "string"
77
+ ? manifest.scripts[scriptName]
78
+ : null;
79
+ }
80
+
81
+ function patternRoot(pattern) {
82
+ return pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern;
83
+ }
84
+
85
+ function inside(root, candidate) {
86
+ const relative = path.relative(root, candidate);
87
+ return relative === ""
88
+ || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
89
+ }
90
+
91
+ function temporaryDirectoryBinding({ root, envelope, needed }) {
92
+ const declared = Object.hasOwn(envelope.environment.values, "TMPDIR")
93
+ ? envelope.environment.values.TMPDIR
94
+ : null;
95
+ const resolved = declared === null
96
+ ? null
97
+ : path.resolve(root, envelope.cwd, declared);
98
+ const writableRoots = envelope.paths.disposableScratch
99
+ .filter(pattern => pattern.endsWith("/**"))
100
+ .map(pattern => path.resolve(root, patternRoot(pattern)));
101
+ const matchedRoot = resolved === null
102
+ ? null
103
+ : writableRoots.find(writable => inside(writable, resolved)) ?? null;
104
+ return {
105
+ needed,
106
+ environmentName: "TMPDIR",
107
+ declaredValue: declared,
108
+ resolvedPath: resolved,
109
+ scratchRoot: matchedRoot,
110
+ disposition: !needed
111
+ ? "not_required"
112
+ : resolved !== null && inside(root, resolved) && matchedRoot !== null
113
+ ? "bound_to_disposable_scratch"
114
+ : declared === null
115
+ ? "not_declared"
116
+ : "outside_disposable_scratch_tree",
117
+ };
118
+ }
119
+
120
+ function detectedScriptTools(script) {
121
+ if (script === null) return [];
122
+ const alternatives = KNOWN_SCRIPT_TOOLS.join("|");
123
+ const pattern = new RegExp(
124
+ `(?:^|[\\s;&|])(?:[^\\s;&|/]+/)*(${alternatives})(?=\\s|$|[;&|])`,
125
+ "giu",
126
+ );
127
+ return [...new Set(
128
+ [...script.matchAll(pattern)]
129
+ .map(match => match[1].toLowerCase()),
130
+ )].sort();
131
+ }
132
+
133
+ function issue({ code, severity, phase, message, repairCommand }) {
134
+ return { code, severity, phase, message, repairCommand };
135
+ }
136
+
137
+ function existingRegularRealpath(value) {
138
+ try {
139
+ const resolved = realpathSync(value);
140
+ const stat = lstatSync(resolved);
141
+ if (!stat.isFile()) return null;
142
+ return resolved;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ function packageToolExecutables({ root, cwd, detectedTools }) {
149
+ const packageRoot = path.join(root, cwd);
150
+ const values = [];
151
+ for (const tool of detectedTools) {
152
+ const launcher = existingRegularRealpath(
153
+ path.join(packageRoot, "node_modules", ".bin", tool),
154
+ );
155
+ if (launcher) values.push(launcher);
156
+ }
157
+ if (detectedTools.some(tool => NATIVE_TOOLCHAIN_TOOLS.has(tool))) {
158
+ for (const candidate of [
159
+ path.join(packageRoot, "node_modules", "esbuild", "bin", "esbuild"),
160
+ path.join(packageRoot, "node_modules", "@esbuild", "darwin-arm64", "bin", "esbuild"),
161
+ path.join(packageRoot, "node_modules", "@esbuild", "darwin-x64", "bin", "esbuild"),
162
+ path.join(packageRoot, "node_modules", "@esbuild", "linux-arm64", "bin", "esbuild"),
163
+ path.join(packageRoot, "node_modules", "@esbuild", "linux-x64", "bin", "esbuild"),
164
+ ]) {
165
+ const resolved = existingRegularRealpath(candidate);
166
+ if (resolved) values.push(resolved);
167
+ }
168
+ }
169
+ return [...new Set(values)].sort();
170
+ }
171
+
172
+ function requiredSubprocesses({ root, cwd, invocation, detectedTools, platform }) {
173
+ const required = [];
174
+ if (new Set(["npm", "pnpm", "yarn"]).has(invocation.tool)) {
175
+ if (existsSync("/bin/sh")) required.push(realpathSync("/bin/sh"));
176
+ }
177
+ if (platform === "darwin" && invocation.tool === "git" && existsSync("/usr/bin/xcrun")) {
178
+ required.push(realpathSync("/usr/bin/xcrun"));
179
+ }
180
+ required.push(...packageToolExecutables({ root, cwd, detectedTools }));
181
+ return [...new Set(required)].sort();
182
+ }
183
+
184
+ function phaseRows(envelope) {
185
+ return ["setup", "check", "teardown"]
186
+ .map(name => ({ name, command: envelope.phases[name] }))
187
+ .filter(row => row.command !== null);
188
+ }
189
+
190
+ export function preflightFormalEnvelopeV1({
191
+ workspaceRoot,
192
+ envelope,
193
+ platform = process.platform,
194
+ backend = resolveVerificationEnvelopeBackendV1({ platform }),
195
+ } = {}) {
196
+ const root = realpathSync(workspaceRoot);
197
+ const validated = validateVerificationEnvelopeV1({ workspaceRoot: root, envelope });
198
+ const runtimeIsolation = inspectRuntimeIsolationV1({ workspaceRoot: root });
199
+ const issues = [];
200
+ if (!backend.available) {
201
+ issues.push(issue({
202
+ code: "envelope_capability_missing",
203
+ severity: "blocker",
204
+ phase: null,
205
+ message: `No Formal enforcement backend is available: ${backend.reason}.`,
206
+ repairCommand: "Use a supported enforcement backend; do not weaken the envelope.",
207
+ }));
208
+ }
209
+
210
+ const phases = phaseRows(validated.envelope).map(({ name, command }) => {
211
+ const invocation = effectiveInvocation(command);
212
+ const scriptName = packageScriptName(invocation);
213
+ const script = readPackageScript(root, validated.envelope.cwd, scriptName);
214
+ const detectedTools = detectedScriptTools(script);
215
+ const requiredExecutables = requiredSubprocesses({
216
+ root,
217
+ cwd: validated.envelope.cwd,
218
+ invocation,
219
+ detectedTools,
220
+ platform,
221
+ });
222
+ const missingExecutables = requiredExecutables.filter(executable => (
223
+ validated.envelope.process.allowSubprocesses !== true
224
+ || !validated.envelope.process.allowedExecutables.includes(executable)
225
+ ));
226
+ if (missingExecutables.length > 0) {
227
+ issues.push(issue({
228
+ code: "subprocess_capability_missing",
229
+ severity: "blocker",
230
+ phase: name,
231
+ message: `${invocation.tool} requires subprocess executables not admitted by the envelope: ${missingExecutables.join(", ")}.`,
232
+ repairCommand: `Set process.allowSubprocesses=true and add the exact regular executable paths: ${missingExecutables.join(", ")}.`,
233
+ }));
234
+ }
235
+ const scanRoot = path.resolve(root, validated.envelope.cwd);
236
+ const runtimeRoot = path.resolve(root, ".owlcoda/runkit");
237
+ const phaseRuntimeIsolation = inspectRuntimeIsolationV1({ workspaceRoot: scanRoot });
238
+ const affectedRuntimeScripts = scriptName === null
239
+ ? []
240
+ : phaseRuntimeIsolation.affectedScripts.filter(row => row.script === scriptName);
241
+ const runtimeCollector = affectedRuntimeScripts.length > 0;
242
+ if (scriptName !== null
243
+ && runtimeCollector
244
+ && inside(scanRoot, runtimeRoot)) {
245
+ issues.push(issue({
246
+ code: "runkit_runtime_scan_conflict",
247
+ severity: "blocker",
248
+ phase: name,
249
+ message: `Project script ${scriptName} broadly scans the workspace and can enter the protected RunKit runtime root.`,
250
+ repairCommand: `Exclude ${runtimeIsolation.recommendedIgnore} through the tool's supported ignore configuration.`,
251
+ }));
252
+ }
253
+ const needsTemporaryDirectory = detectedTools.some(tool => NATIVE_TOOLCHAIN_TOOLS.has(tool));
254
+ const temporaryDirectory = temporaryDirectoryBinding({
255
+ root,
256
+ envelope: validated.envelope,
257
+ needed: needsTemporaryDirectory,
258
+ });
259
+ if (temporaryDirectory.disposition === "outside_disposable_scratch_tree") {
260
+ issues.push(issue({
261
+ code: "temporary_directory_not_declared",
262
+ severity: "blocker",
263
+ phase: name,
264
+ message: "TMPDIR is declared outside a disposableScratch directory tree.",
265
+ repairCommand: "Bind TMPDIR to a directory covered by one declared disposableScratch/** pattern.",
266
+ }));
267
+ } else if (temporaryDirectory.disposition === "not_declared") {
268
+ issues.push(issue({
269
+ code: "temporary_directory_may_be_required",
270
+ severity: "warning",
271
+ phase: name,
272
+ message: `${detectedTools.join(", ")} may require a writable temporary directory or native helper process.`,
273
+ repairCommand: "If the tool requires temporary writes, bind TMPDIR to a declared disposableScratch/** directory and admit only the exact helper executable.",
274
+ }));
275
+ }
276
+ const devicePaths = [...command.argv, ...Object.values(validated.envelope.environment.values)]
277
+ .filter(value => typeof value === "string" && value.startsWith("/dev/"));
278
+ if (devicePaths.length > 0) {
279
+ issues.push(issue({
280
+ code: "device_path_dependency_detected",
281
+ severity: "warning",
282
+ phase: name,
283
+ message: `The command declares device paths that may be incompatible with the sandbox: ${[...new Set(devicePaths)].join(", ")}.`,
284
+ repairCommand: "Replace device-path dependencies with a declared disposableScratch file when possible.",
285
+ }));
286
+ }
287
+ return {
288
+ name,
289
+ executable: command.executable,
290
+ argv: [...command.argv],
291
+ effectiveTool: invocation.tool,
292
+ packageScript: scriptName,
293
+ packageScriptCommand: script,
294
+ detectedTools,
295
+ requiredExecutables,
296
+ temporaryDirectory,
297
+ runtimeScan: {
298
+ scanRoot,
299
+ protectedRuntimeRoot: runtimeRoot,
300
+ canReachProtectedRuntime: inside(scanRoot, runtimeRoot),
301
+ collectorDetected: runtimeCollector,
302
+ affectedScripts: affectedRuntimeScripts,
303
+ },
304
+ };
305
+ });
306
+
307
+ const orderedIssues = issues.sort((left, right) => (
308
+ `${left.phase ?? ""}\u0000${left.code}`.localeCompare(`${right.phase ?? ""}\u0000${right.code}`)
309
+ ));
310
+ const blocked = orderedIssues.some(row => row.severity === "blocker");
311
+ return {
312
+ schemaVersion: PREFLIGHT_SCHEMA,
313
+ status: blocked ? "blocked" : "ready",
314
+ exitCode: blocked ? 2 : 0,
315
+ envelopeId: validated.envelope.envelopeId,
316
+ envelopeSha256: validated.envelopeSha256,
317
+ backend,
318
+ phases,
319
+ runtimeIsolation,
320
+ issues: orderedIssues,
321
+ nextAllowedAction: blocked
322
+ ? "repair_envelope_and_repeat_preflight"
323
+ : "run_formal_check_with_exact_envelope",
324
+ authorizationGranted: false,
325
+ };
326
+ }
327
+
328
+ export function formatFormalEnvelopePreflightHumanV1(result) {
329
+ const lines = [
330
+ `Formal envelope preflight: ${result.status.toUpperCase()}`,
331
+ `Backend: ${result.backend.id}${result.backend.available ? "" : " (unavailable)"}`,
332
+ ];
333
+ lines.push("Phases:");
334
+ for (const phase of result.phases) {
335
+ lines.push(` ${phase.name}: ${[phase.executable, ...phase.argv].join(" ")}`);
336
+ lines.push(` Required subprocesses: ${phase.requiredExecutables.join(", ") || "none"}`);
337
+ lines.push(
338
+ ` Temporary directory: ${phase.temporaryDirectory.disposition}`
339
+ + `${phase.temporaryDirectory.resolvedPath ? ` (${phase.temporaryDirectory.resolvedPath})` : ""}`,
340
+ );
341
+ }
342
+ lines.push(
343
+ `RunKit runtime scan: ${result.runtimeIsolation.status}; protected root .owlcoda/runkit/.`,
344
+ );
345
+ for (const row of result.issues) {
346
+ lines.push(`${row.severity.toUpperCase()} ${row.code}${row.phase ? ` [${row.phase}]` : ""}: ${row.message}`);
347
+ lines.push(` Fix: ${row.repairCommand}`);
348
+ }
349
+ lines.push(`Next: ${result.nextAllowedAction}`);
350
+ lines.push("Authority: no Git, release, deployment, production, or business authorization granted.");
351
+ return `${lines.join("\n")}\n`;
352
+ }