owlrunkit 0.19.0 → 0.19.2

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 (36) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +81 -3
  3. package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +12 -9
  4. package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +36 -0
  5. package/docs/architecture/OWLCODA_RUNKIT_0192_FORMAL_USABILITY_PRODUCT_DECISION_20260813.md +141 -0
  6. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +52 -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 +35 -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/profile-onboarding.mjs +52 -33
  20. package/scripts/runkit-contract/project-cli-resolver.mjs +33 -3
  21. package/scripts/runkit-contract/project-external-delivery.mjs +16 -9
  22. package/scripts/runkit-contract/quick-attest.mjs +24 -0
  23. package/scripts/runkit-contract/runkit-cli.mjs +365 -49
  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-project-event-v5.schema.json +89 -0
  29. package/scripts/runkit-contract/schemas/team-project-status-v3.schema.json +173 -0
  30. package/scripts/runkit-contract/snapshot.mjs +125 -5
  31. package/scripts/runkit-contract/source-candidate.mjs +271 -69
  32. package/scripts/runkit-contract/sync-release-identity.mjs +39 -4
  33. package/scripts/runkit-contract/team-project-successor.mjs +7 -4
  34. package/scripts/runkit-contract/team-project.mjs +440 -28
  35. package/scripts/runkit-contract/usage-mode.mjs +9 -3
  36. package/scripts/runkit-contract/verification-envelope.mjs +425 -37
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
3
3
  "status": "release_candidate_unpublished",
4
4
  "packageName": "owlrunkit",
5
- "candidateVersion": "0.19.0",
5
+ "candidateVersion": "0.19.2",
6
6
  "registry": "https://registry.npmjs.org",
7
7
  "registryRequiredBeforeAdoption": true,
8
8
  "exactVersionRequired": true,
@@ -23,10 +23,10 @@
23
23
  ],
24
24
  "currentPublishedRelease": {
25
25
  "packageName": "owlrunkit",
26
- "version": "0.18.4",
27
- "shasum": "8f2ef01da287b76b545eeebd6973bc40449dd302",
28
- "integrity": "sha512-L3cFuJnU39p/EhgrRaJbEIB7gTRFxKzEpNmANw3kwNFrTe9Fe8wtwgdFOBXldX6xCJY+W44/8cw4P/dtAxiCAA==",
29
- "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.18.4.tgz"
26
+ "version": "0.19.1",
27
+ "shasum": "e14aac84fb680ec9051d786bc84848c8f3ca0ca4",
28
+ "integrity": "sha512-I9sd41tD4kDXz8fOuZqMC06JOmofFDitveInGOPbsGJH5HCp7oa+dMdBCSO2KJjdfsVDXoiOPiTpxfahbTUfrg==",
29
+ "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.19.1.tgz"
30
30
  },
31
31
  "registryRelease": null,
32
32
  "registryEvidenceMustBeExternal": true,
package/sbom.spdx.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "owlrunkit-0.19.0",
6
- "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.19.0",
5
+ "name": "owlrunkit-0.19.2",
6
+ "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.19.2",
7
7
  "creationInfo": {
8
8
  "created": "2026-07-29T00:00:00Z",
9
9
  "creators": [
@@ -14,8 +14,8 @@
14
14
  {
15
15
  "name": "owlrunkit",
16
16
  "SPDXID": "SPDXRef-Package-OwlRunKit",
17
- "versionInfo": "0.19.0",
18
- "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.19.0.tgz",
17
+ "versionInfo": "0.19.2",
18
+ "downloadLocation": "NOASSERTION",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "GPL-3.0-or-later",
21
21
  "licenseDeclared": "GPL-3.0-or-later",
@@ -24,7 +24,7 @@
24
24
  {
25
25
  "referenceCategory": "PACKAGE-MANAGER",
26
26
  "referenceType": "purl",
27
- "referenceLocator": "pkg:npm/owlrunkit@0.19.0"
27
+ "referenceLocator": "pkg:npm/owlrunkit@0.19.2"
28
28
  }
29
29
  ]
30
30
  }
@@ -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.0";
17
+ export const CORE_VERSION = "0.19.2";
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",
@@ -104,6 +106,10 @@ const CORE_FILES = [
104
106
  "schemas/fleet-discovery-v1.schema.json",
105
107
  "schemas/fleet-manifest-v1.schema.json",
106
108
  "schemas/fleet-root-registry-v1.schema.json",
109
+ "schemas/formal-check-v2.schema.json",
110
+ "schemas/formal-envelope-preflight-v1.schema.json",
111
+ "schemas/formal-evidence-summary-v1.schema.json",
112
+ "schemas/formal-supplemental-evidence-v1.schema.json",
107
113
  "schemas/human-status-v1.schema.json",
108
114
  "schemas/onboarding-bootstrap-plan-v1.schema.json",
109
115
  "schemas/onboarding-bootstrap-receipt-v1.schema.json",
@@ -148,9 +154,11 @@ const CORE_FILES = [
148
154
  "schemas/team-project-event-v2.schema.json",
149
155
  "schemas/team-project-event-v3.schema.json",
150
156
  "schemas/team-project-event-v4.schema.json",
157
+ "schemas/team-project-event-v5.schema.json",
151
158
  "schemas/team-project-successor-receipt-v1.schema.json",
152
159
  "schemas/team-project-status-v1.schema.json",
153
160
  "schemas/team-project-status-v2.schema.json",
161
+ "schemas/team-project-status-v3.schema.json",
154
162
  "schemas/usage-mode-recommendation-v1.schema.json",
155
163
  "schemas/usage-mode-recommendation-v2.schema.json",
156
164
  "schemas/verification-envelope-v1.schema.json",
@@ -191,6 +199,7 @@ const CORE_DEPENDENCY_FILES = [
191
199
  "packages/attest/src/formal.mjs",
192
200
  "packages/attest/src/offline-bundle.mjs",
193
201
  "packages/attest/src/quick-receipt-contract.mjs",
202
+ "packages/attest/src/quick.mjs",
194
203
  "packages/attest/src/reference-contract.mjs",
195
204
  "packages/attest/src/workspace-snapshot.mjs",
196
205
  ];
@@ -358,10 +367,34 @@ function coreDirectory() {
358
367
  return resolve(fileURLToPath(new URL(".", import.meta.url)));
359
368
  }
360
369
 
370
+ function dependencyBytesForManifest(projectRoot, name) {
371
+ const bytes = readFileSync(resolve(projectRoot, name));
372
+ if (name !== "packages/attest/src/quick.mjs") return bytes;
373
+ const source = bytes.toString("utf8");
374
+ const escapedVersion = CORE_VERSION.replaceAll(".", "\\.");
375
+ // Normalize only the self-referential release field. The public attester still
376
+ // uses the fixed catalog; release synchronization fills this exact hash later.
377
+ const currentIdentity = new RegExp(
378
+ `(coreVersion:\\s*"${escapedVersion}",\\s*coreManifestSha256:\\s*")`
379
+ + "sha256:[a-f0-9]{64}(\")",
380
+ "gu",
381
+ );
382
+ const matches = [...source.matchAll(currentIdentity)];
383
+ if (matches.length !== 1) {
384
+ throw new Error("Core-bound Quick attester must declare exactly one current release identity.");
385
+ }
386
+ return Buffer.from(source.replace(
387
+ currentIdentity,
388
+ `$1sha256:${"0".repeat(64)}$2`,
389
+ ));
390
+ }
391
+
361
392
  function coreManifestStreamFromRoots(coreRoot, projectRoot) {
362
393
  return [
363
394
  ...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`),
395
+ ...CORE_DEPENDENCY_FILES.map((name) => (
396
+ `${name}\tsha256:${sha256(dependencyBytesForManifest(projectRoot, name))}\n`
397
+ )),
365
398
  ].join("");
366
399
  }
367
400
 
@@ -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
+ }