@b00dea/cockpit 0.0.0-stage → 0.1.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 (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +22 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +74 -4
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,437 @@
1
+ import { sha256Hex } from '../sha256.js';
2
+ import { isAbsolute, normalize } from 'node:path';
3
+ import { authenticatedExecutionOutcome } from '../environment-exec/execute.js';
4
+ import { RefusalError, ENVIRONMENT_CODES, EXECUTION_FAILURE_CODES, snapshotJson, snapshotExecutionPlan, forwardedRefusal, } from './contract.js';
5
+ import { classifyResolvedRuntimeLiterals } from './runtime.js';
6
+ import { isSensitiveField, redactSensitiveText } from './sensitive.js';
7
+ import { isResolvedDockerExecutable } from './executable.js';
8
+ import { exactKeys } from '../exact-keys.js';
9
+ const SHA256 = /^[a-f0-9]{64}$/;
10
+ const STACK_ID = /^stack:[a-f0-9]{64}$/;
11
+ const CONTAINER_ID = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
12
+ const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
13
+ const MAX_TIMEOUT_MS = 30_000;
14
+ // Exported so a caller passes the capture's own bound: the capture prints one stat line and one
15
+ // digest line, and a general output limit sized for logs exceeds what this planner admits.
16
+ export const STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES = 65_536;
17
+ const UNAVAILABLE_EVIDENCE_CODES = new Set([ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE, ...EXECUTION_FAILURE_CODES]);
18
+ const PROCESS_STEP_PREFIX = 'evidence:process-identity:';
19
+ const CONTENT_STEP_PREFIX = 'evidence:content-hash:';
20
+ const PROCESS_SCRIPT = 'IFS= read -r stat < /proc/1/stat; pid=${stat%% *}; rest=${stat##*) }; set -- $rest; printf "%s %s\\n" "$pid" "${20}"';
21
+ // -H follows a symlink named as a content path: /proc/1/exe is one, and without -H find skips it,
22
+ // hashes nothing, and the capture fails with an empty corpus on every real container.
23
+ const CONTENT_SCRIPT = 'tmp=${TMPDIR:-/tmp}/cockpit-content-$$; cockpit_hash_failure=$tmp.failed; export cockpit_hash_failure; trap \'rm -f "$tmp.raw" "$tmp.sorted" "$tmp.failed"\' EXIT HUP INT TERM; find -H "$@" -type f -exec sh -c \'sha256sum -- "$@" || { : > "$cockpit_hash_failure"; exit 1; }\' sh {} + > "$tmp.raw"; [ ! -e "$tmp.failed" ] && [ -s "$tmp.raw" ]; LC_ALL=C sort "$tmp.raw" > "$tmp.sorted"; sha256sum "$tmp.sorted"';
24
+ function stableValue(value) {
25
+ if (Array.isArray(value))
26
+ return `[${value.map(stableValue).join(',')}]`;
27
+ if (value !== null && typeof value === 'object') {
28
+ return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
29
+ .map(([key, child]) => `${JSON.stringify(key)}:${stableValue(child)}`).join(',')}}`;
30
+ }
31
+ return JSON.stringify(value);
32
+ }
33
+ function ownedObject(value) {
34
+ const snapshot = snapshotJson(value);
35
+ return snapshot !== undefined && snapshot !== null && typeof snapshot === 'object' && !Array.isArray(snapshot)
36
+ ? snapshot
37
+ : undefined;
38
+ }
39
+ function sanitize(value, key = '') {
40
+ if (isSensitiveField(key))
41
+ return '[redacted]';
42
+ if (Array.isArray(value))
43
+ return value.map((item) => sanitize(item));
44
+ if (value !== null && typeof value === 'object') {
45
+ return Object.fromEntries(Object.entries(value).map(([name, child]) => [name, sanitize(child, name)]));
46
+ }
47
+ return typeof value === 'string' ? redactSensitiveText(value) : value;
48
+ }
49
+ function validInstant(value) {
50
+ if (!ISO_INSTANT.test(value))
51
+ return false;
52
+ const date = new Date(value);
53
+ const canonical = value.includes('.') ? value : value.replace('Z', '.000Z');
54
+ return !Number.isNaN(date.valueOf()) && date.toISOString() === canonical;
55
+ }
56
+ function instantMs(value) { return new Date(value).valueOf(); }
57
+ function diagnostic(message, target) {
58
+ return { severity: 'error', code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE, message, target };
59
+ }
60
+ function refusal(message, target) {
61
+ return forwardedRefusal([diagnostic(message, target)]);
62
+ }
63
+ export function createEnvironmentSnapshot(input) {
64
+ const owned = ownedObject(input);
65
+ if (owned === undefined || !exactKeys(owned, ['configSha256', 'createdAt', 'creationSource', 'intendedState', 'stackId'])
66
+ || typeof owned.stackId !== 'string' || typeof owned.configSha256 !== 'string' || typeof owned.createdAt !== 'string'
67
+ || !STACK_ID.test(owned.stackId) || !SHA256.test(owned.configSha256) || !validInstant(owned.createdAt)
68
+ || owned.creationSource !== 'resolved-config') {
69
+ throw new RefusalError('environment snapshot provenance is invalid');
70
+ }
71
+ const intendedState = sanitize(owned.intendedState);
72
+ const snapshotSha256 = sha256Hex(stableValue({
73
+ stackId: owned.stackId,
74
+ configSha256: owned.configSha256,
75
+ createdAt: owned.createdAt,
76
+ creationSource: owned.creationSource,
77
+ intendedState,
78
+ }));
79
+ return {
80
+ artifact: 'generated-environment-snapshot', schemaVersion: 1, stackId: owned.stackId,
81
+ configSha256: owned.configSha256, createdAt: owned.createdAt,
82
+ creationSource: owned.creationSource, intendedState, snapshotSha256,
83
+ };
84
+ }
85
+ function stabilityCapturePlanInput(value) {
86
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
87
+ || !exactKeys(value, ['stackId', 'configSha256', 'runtimeExecutable', 'cwd', 'env', 'containerId', 'batchId', 'runtime', 'contentPaths', 'timeoutMs', 'outputLimitBytes']))
88
+ return undefined;
89
+ const input = value;
90
+ if (!isResolvedDockerExecutable(input.runtimeExecutable)
91
+ || input.runtime === null || typeof input.runtime !== 'object' || Array.isArray(input.runtime)
92
+ || !exactKeys(input.runtime, ['name', 'version']) || Object.values(input.runtime).some((part) => typeof part !== 'string')
93
+ || input.env === null || typeof input.env !== 'object' || Array.isArray(input.env)
94
+ || Object.entries(input.env).some(([name, part]) => name.length === 0 || typeof part !== 'string')
95
+ || !Array.isArray(input.contentPaths) || input.contentPaths.some((path) => typeof path !== 'string')
96
+ || typeof input.stackId !== 'string' || typeof input.configSha256 !== 'string' || typeof input.cwd !== 'string'
97
+ || typeof input.containerId !== 'string' || typeof input.batchId !== 'string'
98
+ || typeof input.timeoutMs !== 'number' || typeof input.outputLimitBytes !== 'number')
99
+ return undefined;
100
+ return input;
101
+ }
102
+ function captureStep(input, id, argv) {
103
+ return {
104
+ id,
105
+ argv: [...argv],
106
+ argv0: input.runtimeExecutable.invocationPath,
107
+ cwd: input.cwd,
108
+ env: { ...input.env },
109
+ timeoutMs: input.timeoutMs,
110
+ stdoutLimitBytes: input.outputLimitBytes,
111
+ stderrLimitBytes: input.outputLimitBytes,
112
+ paths: [...input.contentPaths],
113
+ ports: [],
114
+ labels: { stackId: input.stackId, configSha256: input.configSha256, containerId: input.containerId, batchId: input.batchId, runtimeName: input.runtime.name, runtimeVersion: input.runtime.version, executableSha256: input.runtimeExecutable.sha256, evidenceRole: id.startsWith(PROCESS_STEP_PREFIX) ? 'process-identity' : 'content-hash' },
115
+ expected: [],
116
+ consumedFiles: [{ path: input.runtimeExecutable.canonicalPath, sha256: input.runtimeExecutable.sha256, maxBytes: 536_870_912, argvIndexes: [0], executable: true, executionMode: input.runtimeExecutable.executionMode }],
117
+ filesystemChecks: [],
118
+ compensation: [],
119
+ };
120
+ }
121
+ export function createStabilityCapturePlan(rawInput) {
122
+ const input = stabilityCapturePlanInput(snapshotJson(rawInput));
123
+ if (input === undefined)
124
+ return refusal('stability capture input must be readable JSON data', 'stability-capture');
125
+ const pathsValid = input.contentPaths.length > 0 && new Set(input.contentPaths).size === input.contentPaths.length
126
+ && input.contentPaths.every((path) => isAbsolute(path) && normalize(path) === path && !path.includes('\0'));
127
+ if (!STACK_ID.test(input.stackId) || !SHA256.test(input.configSha256)
128
+ || !isResolvedDockerExecutable(input.runtimeExecutable)
129
+ || !isAbsolute(input.cwd) || normalize(input.cwd) !== input.cwd || !CONTAINER_ID.test(input.containerId) || !SHA256.test(input.batchId)
130
+ || input.runtime.name !== 'docker' || !/^\d+\.\d+\.\d+$/.test(input.runtime.version)
131
+ || !pathsValid || !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > MAX_TIMEOUT_MS
132
+ || !Number.isInteger(input.outputLimitBytes) || input.outputLimitBytes < 1 || input.outputLimitBytes > STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES) {
133
+ return refusal('stability capture requires resolved in-container targets and bounded execution', input.containerId);
134
+ }
135
+ if (Object.keys(input.env).some((name) => name.startsWith('DOCKER_') || name.startsWith('COMPOSE_'))
136
+ || classifyResolvedRuntimeLiterals([...Object.keys(input.env), ...Object.values(input.env)]) !== 'resolved') {
137
+ return refusal('stability capture cannot accept decision-bearing runtime environment', input.containerId);
138
+ }
139
+ const steps = [
140
+ captureStep(input, `${PROCESS_STEP_PREFIX}${input.containerId}`, [
141
+ input.runtimeExecutable.canonicalPath, 'exec', '--', input.containerId,
142
+ '/bin/sh', '-eu', '-c', PROCESS_SCRIPT,
143
+ ]),
144
+ captureStep(input, `${CONTENT_STEP_PREFIX}${input.containerId}`, [
145
+ input.runtimeExecutable.canonicalPath, 'exec', '--', input.containerId,
146
+ '/bin/sh', '-eu', '-c', CONTENT_SCRIPT, 'cockpit-content-hash', ...input.contentPaths,
147
+ ]),
148
+ ];
149
+ const plan = snapshotExecutionPlan({ steps });
150
+ return plan === undefined
151
+ ? refusal('stability capture did not produce a valid execution plan', input.containerId)
152
+ : { kind: 'preview', status: 0, diagnostics: [], result: null, plan };
153
+ }
154
+ const authenticatedCaptureBrand = Symbol('authenticated-stability-capture');
155
+ const issuedCaptures = new WeakMap();
156
+ function ownedResults(value) {
157
+ return Array.isArray(value) && value.length === 2 ? [value[0], value[1]] : undefined;
158
+ }
159
+ function captureAuthenticationInput(value) {
160
+ if (value === null || typeof value !== 'object' || Array.isArray(value) || !exactKeys(value, ['plan', 'results']))
161
+ return undefined;
162
+ const { plan, results } = value;
163
+ return { plan, results };
164
+ }
165
+ export function authenticateStabilityCapture(input) {
166
+ const ownedInput = captureAuthenticationInput(input);
167
+ if (ownedInput === undefined)
168
+ return unavailable('stability capture input is not readable');
169
+ const plan = ownedObject(ownedInput.plan);
170
+ const capturedResults = ownedResults(ownedInput.results);
171
+ if (plan === undefined || capturedResults === undefined || !exactKeys(plan, ['kind', 'status', 'diagnostics', 'result', 'plan'])
172
+ || plan.kind !== 'preview' || plan.status !== 0 || plan.result !== null || !Array.isArray(plan.diagnostics) || plan.diagnostics.length !== 0) {
173
+ return unavailable('stability capture input is not readable');
174
+ }
175
+ const executionPlan = snapshotExecutionPlan(plan.plan);
176
+ if (executionPlan === undefined)
177
+ return unavailable('stability capture input is not readable');
178
+ const steps = executionPlan.steps;
179
+ if (steps.length !== 2) {
180
+ return unavailable('stability capture requires two complete planned execution results');
181
+ }
182
+ const authenticatedResults = capturedResults.map((result, index) => authenticatedExecutionOutcome(result, steps[index]));
183
+ if (authenticatedResults.some((result) => result === undefined)) {
184
+ return unavailable('stability results do not authenticate against one planned execution cohort');
185
+ }
186
+ const refused = authenticatedResults.find((result) => result?.kind === 'refusal');
187
+ if (refused?.kind === 'refusal')
188
+ return unavailable('stability capture execution was refused', refused.code);
189
+ const results = authenticatedResults;
190
+ const [first, second] = results;
191
+ const labels = steps[0].labels;
192
+ const sameCohort = steps.every((step) => step.labels.stackId === labels.stackId
193
+ && step.labels.configSha256 === labels.configSha256 && step.labels.batchId === labels.batchId
194
+ && step.labels.containerId === labels.containerId && step.labels.runtimeName === labels.runtimeName
195
+ && step.labels.runtimeVersion === labels.runtimeVersion && step.labels.executableSha256 === labels.executableSha256
196
+ && step.argv0 === steps[0].argv0);
197
+ const executable = steps[0].argv[0];
198
+ const exactCommands = steps[0].argv.length === 8 && steps[0].argv.slice(1).join('\0') === ['exec', '--', labels.containerId, '/bin/sh', '-eu', '-c', PROCESS_SCRIPT].join('\0')
199
+ && steps[1].argv.length > 9 && steps[1].argv.slice(1, 8).join('\0') === ['exec', '--', labels.containerId, '/bin/sh', '-eu', '-c', CONTENT_SCRIPT].join('\0')
200
+ && steps[1].argv[8] === 'cockpit-content-hash'
201
+ && isAbsolute(steps[0].argv0) && normalize(steps[0].argv0) === steps[0].argv0
202
+ && steps.every((step) => step.argv[0] === executable && step.filesystemChecks.length === 0
203
+ && step.consumedFiles.length === 1 && step.consumedFiles[0].path === executable
204
+ && step.consumedFiles[0].sha256 === labels.executableSha256
205
+ && step.consumedFiles[0].executable && step.consumedFiles[0].argvIndexes.length === 1
206
+ && step.consumedFiles[0].argvIndexes[0] === 0);
207
+ if (!sameCohort || !exactCommands || !STACK_ID.test(labels.stackId ?? '') || !SHA256.test(labels.configSha256 ?? '')
208
+ || !SHA256.test(labels.batchId ?? '') || !CONTAINER_ID.test(labels.containerId ?? '') || labels.runtimeName !== 'docker'
209
+ || !/^\d+\.\d+\.\d+$/.test(labels.runtimeVersion ?? '') || !SHA256.test(labels.executableSha256 ?? '')
210
+ || steps[0].labels.evidenceRole !== 'process-identity' || steps[1].labels.evidenceRole !== 'content-hash'
211
+ || !validInstant(first.receipt.startedAt) || !validInstant(first.receipt.finishedAt)
212
+ || !validInstant(second.receipt.startedAt) || !validInstant(second.receipt.finishedAt)
213
+ || instantMs(first.receipt.startedAt) > instantMs(first.receipt.finishedAt) || instantMs(first.receipt.finishedAt) > instantMs(second.receipt.startedAt)
214
+ || instantMs(second.receipt.startedAt) > instantMs(second.receipt.finishedAt)
215
+ || steps[0].id !== `${PROCESS_STEP_PREFIX}${labels.containerId}` || steps[1].id !== `${CONTENT_STEP_PREFIX}${labels.containerId}`
216
+ || first.receipt.stepId !== steps[0].id || second.receipt.stepId !== steps[1].id) {
217
+ return unavailable('stability results do not authenticate against one planned execution cohort');
218
+ }
219
+ const stdout = first.stdout.byteLength + second.stdout.byteLength;
220
+ const stderr = first.stderr.byteLength + second.stderr.byteLength;
221
+ if (results.some((result, index) => result.stdout.byteLength > steps[index].stdoutLimitBytes || result.stderr.byteLength > steps[index].stderrLimitBytes
222
+ || result.execution.stdoutRetainedBytes !== result.stdout.byteLength || result.execution.stderrRetainedBytes !== result.stderr.byteLength
223
+ || result.execution.stdoutObservedBytes !== result.stdout.byteLength || result.execution.stderrObservedBytes !== result.stderr.byteLength))
224
+ return unavailable('stability result exceeded its planned limit');
225
+ let processIdentity;
226
+ let contentHash;
227
+ try {
228
+ processIdentity = new TextDecoder('utf-8', { fatal: true }).decode(first.stdout).trim();
229
+ contentHash = new TextDecoder('utf-8', { fatal: true }).decode(second.stdout).trim().split(/\s+/)[0];
230
+ }
231
+ catch { /* Invalid UTF-8 is the only throw here and is what the message names. */
232
+ return unavailable('stability result encoding is invalid');
233
+ }
234
+ if (!/^\d+ \d+$/.test(processIdentity) || !SHA256.test(contentHash))
235
+ return unavailable('stability result shape is invalid');
236
+ const payload = {
237
+ stackId: labels.stackId, configSha256: labels.configSha256,
238
+ batchId: labels.batchId, containerId: labels.containerId,
239
+ runtime: { name: labels.runtimeName, version: labels.runtimeVersion, executableSha256: labels.executableSha256 }, startedAt: first.receipt.startedAt, finishedAt: second.receipt.finishedAt,
240
+ command: { stepIds: [steps[0].id, steps[1].id], timeoutMs: Math.max(...steps.map(({ timeoutMs }) => timeoutMs)), stdoutLimitBytes: steps.reduce((sum, step) => sum + step.stdoutLimitBytes, 0), stderrLimitBytes: steps.reduce((sum, step) => sum + step.stderrLimitBytes, 0) },
241
+ observedBytes: { stdout, stderr }, processIdentity, contentHash,
242
+ };
243
+ const capture = { [authenticatedCaptureBrand]: true, ...payload };
244
+ issuedCaptures.set(capture, payload);
245
+ return capture;
246
+ }
247
+ function unavailable(reason, code = ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE) {
248
+ return { state: 'unavailable', code, reason };
249
+ }
250
+ export function captureRuntimeEvidence(input) {
251
+ if (input === null || typeof input !== 'object' || Array.isArray(input) || !exactKeys(input, ['capture'])
252
+ || input.capture === null || typeof input.capture !== 'object') {
253
+ return unavailable('runtime evidence capture is not readable');
254
+ }
255
+ const capture = issuedCaptures.get(input.capture);
256
+ if (capture === undefined)
257
+ return unavailable('runtime evidence was not produced by an authenticated capture');
258
+ if (!validAuthenticatedCapture(capture))
259
+ return unavailable('runtime evidence provenance is invalid');
260
+ const [processStepId, contentStepId] = capture.command.stepIds;
261
+ if (!STACK_ID.test(capture.stackId) || !SHA256.test(capture.configSha256)
262
+ || !SHA256.test(capture.runtime.executableSha256) || !/^\d+\.\d+\.\d+$/.test(capture.runtime.version)
263
+ || capture.runtime.name.length === 0 || !SHA256.test(capture.batchId) || !CONTAINER_ID.test(capture.containerId)
264
+ || processStepId !== `${PROCESS_STEP_PREFIX}${capture.containerId}` || contentStepId !== `${CONTENT_STEP_PREFIX}${capture.containerId}`)
265
+ return unavailable('runtime evidence provenance is invalid');
266
+ if (!Number.isSafeInteger(capture.observedBytes.stdout) || !Number.isSafeInteger(capture.observedBytes.stderr)) {
267
+ return unavailable('runtime evidence exceeded or omitted its declared execution limits');
268
+ }
269
+ const observedAt = capture.finishedAt;
270
+ const command = { ...capture.command, batchId: capture.batchId, containerId: capture.containerId, stepIds: [...capture.command.stepIds] };
271
+ const observedBytes = { ...capture.observedBytes };
272
+ const runtime = { ...capture.runtime };
273
+ const stability = {
274
+ processIdentitySha256: sha256Hex(capture.processIdentity),
275
+ contentHash: capture.contentHash,
276
+ };
277
+ const captureStartedAt = capture.startedAt;
278
+ const core = { stackId: capture.stackId, configSha256: capture.configSha256, runtime, observedAt, captureStartedAt, command, observedBytes, stability };
279
+ return { state: 'available', artifact: 'captured-runtime-evidence', schemaVersion: 1, ...core, evidenceSha256: sha256Hex(stableValue(core)) };
280
+ }
281
+ function sameRuntime(left, right) {
282
+ return left.runtime.name === right.runtime.name && left.runtime.version === right.runtime.version
283
+ && left.runtime.executableSha256 === right.runtime.executableSha256;
284
+ }
285
+ function validRuntimeEvidence(value) {
286
+ if (validEvidence(value))
287
+ return true;
288
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
289
+ || !exactKeys(value, ['state', 'code', 'reason']))
290
+ return false;
291
+ const candidate = value;
292
+ return candidate.state === 'unavailable' && typeof candidate.code === 'string'
293
+ && UNAVAILABLE_EVIDENCE_CODES.has(candidate.code) && typeof candidate.reason === 'string';
294
+ }
295
+ export function compareEnvironmentSnapshot(rawSnapshot, rawBefore, rawAfter) {
296
+ const snapshot = ownedObject(rawSnapshot);
297
+ const before = ownedObject(rawBefore);
298
+ const after = ownedObject(rawAfter);
299
+ if (snapshot === undefined || before === undefined || after === undefined) {
300
+ return { verdict: 'unavailable', diagnostics: [diagnostic('environment evidence is not readable', 'environment-evidence')] };
301
+ }
302
+ if (!validSnapshot(snapshot)) {
303
+ return { verdict: 'unavailable', diagnostics: [diagnostic('generated environment snapshot is invalid', 'environment-snapshot')] };
304
+ }
305
+ if (!validRuntimeEvidence(before) || !validRuntimeEvidence(after)) {
306
+ return { verdict: 'unavailable', diagnostics: [diagnostic('captured runtime evidence is invalid', snapshot.stackId)] };
307
+ }
308
+ if (before.state !== 'available' || after.state !== 'available') {
309
+ return { verdict: 'unavailable', diagnostics: [diagnostic('required runtime evidence is unavailable', snapshot.stackId)] };
310
+ }
311
+ if (!isRuntimeEvidenceAuthentic(before) || !isRuntimeEvidenceAuthentic(after)) {
312
+ return { verdict: 'unavailable', diagnostics: [diagnostic('captured runtime evidence is invalid', snapshot.stackId)] };
313
+ }
314
+ if (snapshot.stackId !== before.stackId || snapshot.stackId !== after.stackId
315
+ || snapshot.configSha256 !== before.configSha256 || snapshot.configSha256 !== after.configSha256
316
+ || !sameRuntime(before, after)) {
317
+ return { verdict: 'stale', diagnostics: [diagnostic('evidence authorities changed during comparison', snapshot.stackId)] };
318
+ }
319
+ if (before.command.batchId === after.command.batchId || instantMs(before.observedAt) > instantMs(after.captureStartedAt)) {
320
+ return { verdict: 'unavailable', diagnostics: [diagnostic('evidence batches are replayed or out of order', snapshot.stackId)] };
321
+ }
322
+ const processIdentity = before.stability.processIdentitySha256 === after.stability.processIdentitySha256 ? 'unchanged' : 'moved';
323
+ const contentHash = before.stability.contentHash === after.stability.contentHash ? 'unchanged' : 'moved';
324
+ if (processIdentity === 'moved' || contentHash === 'moved') {
325
+ return {
326
+ verdict: 'void', processIdentity, contentHash,
327
+ diagnostics: [diagnostic('test subject changed while evidence was captured', snapshot.stackId)],
328
+ };
329
+ }
330
+ const diagnostics = [];
331
+ return { verdict: 'stable', processIdentity, contentHash, diagnostics };
332
+ }
333
+ function validSnapshot(value) {
334
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
335
+ || !exactKeys(value, ['artifact', 'schemaVersion', 'stackId', 'configSha256', 'createdAt', 'creationSource', 'intendedState', 'snapshotSha256']))
336
+ return false;
337
+ const snapshot = value;
338
+ if (typeof snapshot.artifact !== 'string' || typeof snapshot.schemaVersion !== 'number'
339
+ || typeof snapshot.stackId !== 'string' || typeof snapshot.configSha256 !== 'string'
340
+ || typeof snapshot.createdAt !== 'string' || typeof snapshot.creationSource !== 'string'
341
+ || typeof snapshot.snapshotSha256 !== 'string' || snapshot.intendedState === undefined)
342
+ return false;
343
+ const core = {
344
+ stackId: snapshot.stackId, configSha256: snapshot.configSha256, createdAt: snapshot.createdAt,
345
+ creationSource: snapshot.creationSource, intendedState: snapshot.intendedState,
346
+ };
347
+ return snapshot.artifact === 'generated-environment-snapshot' && snapshot.schemaVersion === 1
348
+ && STACK_ID.test(snapshot.stackId) && SHA256.test(snapshot.configSha256) && validInstant(snapshot.createdAt)
349
+ && snapshot.creationSource === 'resolved-config' && stableValue(sanitize(snapshot.intendedState)) === stableValue(snapshot.intendedState)
350
+ && SHA256.test(snapshot.snapshotSha256)
351
+ && snapshot.snapshotSha256 === sha256Hex(stableValue(core));
352
+ }
353
+ function validAuthenticatedCapture(value) {
354
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
355
+ || !exactKeys(value, ['stackId', 'configSha256', 'batchId', 'containerId', 'runtime', 'startedAt', 'finishedAt', 'command', 'observedBytes', 'processIdentity', 'contentHash']))
356
+ return false;
357
+ const capture = value;
358
+ return typeof capture.stackId === 'string' && STACK_ID.test(capture.stackId)
359
+ && typeof capture.configSha256 === 'string' && SHA256.test(capture.configSha256)
360
+ && typeof capture.batchId === 'string' && SHA256.test(capture.batchId)
361
+ && typeof capture.containerId === 'string' && CONTAINER_ID.test(capture.containerId)
362
+ && capture.runtime !== null && typeof capture.runtime === 'object' && !Array.isArray(capture.runtime)
363
+ && exactKeys(capture.runtime, ['name', 'version', 'executableSha256'])
364
+ && typeof capture.runtime.name === 'string' && capture.runtime.name.length > 0
365
+ && typeof capture.runtime.version === 'string' && /^\d+\.\d+\.\d+$/.test(capture.runtime.version)
366
+ && typeof capture.runtime.executableSha256 === 'string' && SHA256.test(capture.runtime.executableSha256)
367
+ && typeof capture.startedAt === 'string' && validInstant(capture.startedAt)
368
+ && typeof capture.finishedAt === 'string' && validInstant(capture.finishedAt)
369
+ && capture.command !== null && typeof capture.command === 'object' && !Array.isArray(capture.command)
370
+ && exactKeys(capture.command, ['stepIds', 'timeoutMs', 'stdoutLimitBytes', 'stderrLimitBytes'])
371
+ && Array.isArray(capture.command.stepIds) && capture.command.stepIds.length === 2
372
+ && capture.command.stepIds.every((id) => typeof id === 'string')
373
+ && Number.isSafeInteger(capture.command.timeoutMs) && capture.command.timeoutMs > 0 && capture.command.timeoutMs <= MAX_TIMEOUT_MS
374
+ && Number.isSafeInteger(capture.command.stdoutLimitBytes) && capture.command.stdoutLimitBytes > 0 && capture.command.stdoutLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
375
+ && Number.isSafeInteger(capture.command.stderrLimitBytes) && capture.command.stderrLimitBytes > 0 && capture.command.stderrLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
376
+ && capture.observedBytes !== null && typeof capture.observedBytes === 'object' && !Array.isArray(capture.observedBytes)
377
+ && exactKeys(capture.observedBytes, ['stdout', 'stderr'])
378
+ && Number.isSafeInteger(capture.observedBytes.stdout) && capture.observedBytes.stdout >= 0
379
+ && Number.isSafeInteger(capture.observedBytes.stderr) && capture.observedBytes.stderr >= 0
380
+ && capture.observedBytes.stdout <= capture.command.stdoutLimitBytes
381
+ && capture.observedBytes.stderr <= capture.command.stderrLimitBytes
382
+ && typeof capture.processIdentity === 'string' && /^\d+ \d+$/.test(capture.processIdentity)
383
+ && typeof capture.contentHash === 'string' && SHA256.test(capture.contentHash);
384
+ }
385
+ function validEvidence(value) {
386
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
387
+ || !exactKeys(value, ['state', 'artifact', 'schemaVersion', 'stackId', 'configSha256', 'runtime', 'observedAt', 'captureStartedAt', 'command', 'observedBytes', 'stability', 'evidenceSha256']))
388
+ return false;
389
+ const evidence = value;
390
+ if (evidence.runtime === null || typeof evidence.runtime !== 'object' || Array.isArray(evidence.runtime)
391
+ || !exactKeys(evidence.runtime, ['name', 'version', 'executableSha256'])
392
+ || typeof evidence.runtime.name !== 'string' || typeof evidence.runtime.version !== 'string'
393
+ || typeof evidence.runtime.executableSha256 !== 'string'
394
+ || evidence.command === null || typeof evidence.command !== 'object' || Array.isArray(evidence.command)
395
+ || !exactKeys(evidence.command, ['batchId', 'containerId', 'stepIds', 'timeoutMs', 'stdoutLimitBytes', 'stderrLimitBytes'])
396
+ || typeof evidence.command.batchId !== 'string' || typeof evidence.command.containerId !== 'string'
397
+ || !Array.isArray(evidence.command.stepIds) || evidence.command.stepIds.length !== 2
398
+ || evidence.command.stepIds.some((id) => typeof id !== 'string')
399
+ || evidence.observedBytes === null || typeof evidence.observedBytes !== 'object' || Array.isArray(evidence.observedBytes)
400
+ || !exactKeys(evidence.observedBytes, ['stdout', 'stderr'])
401
+ || evidence.stability === null || typeof evidence.stability !== 'object' || Array.isArray(evidence.stability)
402
+ || !exactKeys(evidence.stability, ['processIdentitySha256', 'contentHash'])
403
+ || typeof evidence.stability.processIdentitySha256 !== 'string' || typeof evidence.stability.contentHash !== 'string'
404
+ || typeof evidence.state !== 'string' || typeof evidence.artifact !== 'string' || typeof evidence.schemaVersion !== 'number'
405
+ || typeof evidence.stackId !== 'string' || typeof evidence.configSha256 !== 'string'
406
+ || typeof evidence.observedAt !== 'string' || typeof evidence.captureStartedAt !== 'string'
407
+ || typeof evidence.evidenceSha256 !== 'string')
408
+ return false;
409
+ const core = {
410
+ stackId: evidence.stackId, configSha256: evidence.configSha256, runtime: evidence.runtime,
411
+ observedAt: evidence.observedAt, captureStartedAt: evidence.captureStartedAt,
412
+ command: evidence.command, observedBytes: evidence.observedBytes,
413
+ stability: evidence.stability,
414
+ };
415
+ return evidence.state === 'available' && evidence.artifact === 'captured-runtime-evidence' && evidence.schemaVersion === 1
416
+ && STACK_ID.test(evidence.stackId) && SHA256.test(evidence.configSha256) && validInstant(evidence.observedAt)
417
+ && validInstant(evidence.captureStartedAt) && instantMs(evidence.captureStartedAt) <= instantMs(evidence.observedAt)
418
+ && evidence.runtime.name === 'docker' && /^\d+\.\d+\.\d+$/.test(evidence.runtime.version)
419
+ && SHA256.test(evidence.runtime.executableSha256) && SHA256.test(evidence.command.batchId)
420
+ && CONTAINER_ID.test(evidence.command.containerId)
421
+ && evidence.command.stepIds.length === 2
422
+ && evidence.command.stepIds[0] === `${PROCESS_STEP_PREFIX}${evidence.command.containerId}`
423
+ && evidence.command.stepIds[1] === `${CONTENT_STEP_PREFIX}${evidence.command.containerId}`
424
+ && Number.isSafeInteger(evidence.command.timeoutMs) && evidence.command.timeoutMs > 0 && evidence.command.timeoutMs <= MAX_TIMEOUT_MS
425
+ && Number.isSafeInteger(evidence.command.stdoutLimitBytes) && evidence.command.stdoutLimitBytes > 0 && evidence.command.stdoutLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
426
+ && Number.isSafeInteger(evidence.command.stderrLimitBytes) && evidence.command.stderrLimitBytes > 0 && evidence.command.stderrLimitBytes <= STABILITY_CAPTURE_OUTPUT_LIMIT_BYTES * 2
427
+ && Number.isSafeInteger(evidence.observedBytes.stdout) && evidence.observedBytes.stdout >= 0
428
+ && Number.isSafeInteger(evidence.observedBytes.stderr) && evidence.observedBytes.stderr >= 0
429
+ && evidence.observedBytes.stdout <= evidence.command.stdoutLimitBytes
430
+ && evidence.observedBytes.stderr <= evidence.command.stderrLimitBytes
431
+ && SHA256.test(evidence.stability.processIdentitySha256) && SHA256.test(evidence.stability.contentHash)
432
+ && SHA256.test(evidence.evidenceSha256) && evidence.evidenceSha256 === sha256Hex(stableValue(core));
433
+ }
434
+ export function isRuntimeEvidenceAuthentic(rawEvidence) {
435
+ const evidence = ownedObject(rawEvidence);
436
+ return evidence !== undefined && validEvidence(evidence);
437
+ }
@@ -0,0 +1,26 @@
1
+ export declare const GUARD_EXECUTABLE_SEARCH_PATH = "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin";
2
+ export interface ResolvedExecutable {
3
+ readonly canonicalPath: string;
4
+ readonly invocationPath: string;
5
+ readonly sha256: string;
6
+ readonly executionMode: 'verified-path';
7
+ }
8
+ export declare function resolvedExecutable(value: unknown): ResolvedExecutable | undefined;
9
+ export declare function isResolvedExecutable(value: unknown): boolean;
10
+ export declare function resolvedDockerExecutable(value: unknown): ResolvedExecutable | undefined;
11
+ export declare function isResolvedDockerExecutable(value: unknown): boolean;
12
+ export declare function resolvedGitExecutable(value: unknown): ResolvedExecutable | undefined;
13
+ export declare function isResolvedGitExecutable(value: unknown): boolean;
14
+ export type ExecutableResolution = Readonly<{
15
+ state: 'available';
16
+ value: ResolvedExecutable;
17
+ }> | Readonly<{
18
+ state: 'unavailable';
19
+ reason: string;
20
+ }>;
21
+ export declare function resolveExecutablePath(path: string): Promise<ExecutableResolution>;
22
+ export declare function resolveExecutable(name: string, env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
23
+ export declare function resolveGitExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
24
+ export declare function resolveDiskCapacityExecutable(): Promise<ExecutableResolution>;
25
+ export declare function resolveDockerExecutable(env: Readonly<Record<string, string | undefined>>): Promise<ExecutableResolution>;
26
+ export declare function resolvePathRemovalExecutable(): Promise<ExecutableResolution>;
@@ -0,0 +1,167 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, open, realpath } from 'node:fs/promises';
4
+ import { basename, delimiter, isAbsolute, join, normalize } from 'node:path';
5
+ import { errorCause } from './contract.js';
6
+ import { fileFingerprint, recordVerifiedDigest, verifiedDigest } from '../environment-exec/verified-digest.js';
7
+ import { exactKeys } from '../exact-keys.js';
8
+ // A guard runs inside the agent's hook with an environment the agent controls, so the tools it
9
+ // observes with are found on this fixed search path rather than the inherited PATH.
10
+ export const GUARD_EXECUTABLE_SEARCH_PATH = '/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin';
11
+ const SHA256 = /^[a-f0-9]{64}$/;
12
+ export function resolvedExecutable(value) {
13
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
14
+ || !exactKeys(value, ['canonicalPath', 'invocationPath', 'sha256', 'executionMode']))
15
+ return undefined;
16
+ const { canonicalPath, invocationPath, sha256, executionMode } = value;
17
+ return typeof canonicalPath === 'string' && isAbsolute(canonicalPath) && normalize(canonicalPath) === canonicalPath
18
+ && !canonicalPath.includes('\0') && typeof invocationPath === 'string' && isAbsolute(invocationPath)
19
+ && normalize(invocationPath) === invocationPath && !invocationPath.includes('\0')
20
+ && typeof sha256 === 'string' && SHA256.test(sha256)
21
+ && executionMode === 'verified-path'
22
+ ? { canonicalPath, invocationPath, sha256, executionMode }
23
+ : undefined;
24
+ }
25
+ export function isResolvedExecutable(value) {
26
+ return resolvedExecutable(value) !== undefined;
27
+ }
28
+ export function resolvedDockerExecutable(value) {
29
+ const executable = resolvedExecutable(value);
30
+ return executable !== undefined && basename(executable.invocationPath) === 'docker' ? executable : undefined;
31
+ }
32
+ export function isResolvedDockerExecutable(value) {
33
+ return resolvedDockerExecutable(value) !== undefined;
34
+ }
35
+ export function resolvedGitExecutable(value) {
36
+ const executable = resolvedExecutable(value);
37
+ return executable !== undefined && basename(executable.invocationPath) === 'git' ? executable : undefined;
38
+ }
39
+ export function isResolvedGitExecutable(value) {
40
+ return resolvedGitExecutable(value) !== undefined;
41
+ }
42
+ const EXECUTABLE_BYTE_LIMIT = 536_870_912;
43
+ const PATH_ENTRY_LIMIT = 256;
44
+ function searchPath(env) {
45
+ try {
46
+ return { kind: 'selected', path: env.PATH };
47
+ }
48
+ catch { /* A throwing PATH getter means the environment is unreadable; the caller reports it as unavailable. */
49
+ return { kind: 'unreadable' };
50
+ }
51
+ }
52
+ function unavailable(reason) {
53
+ return { state: 'unavailable', reason };
54
+ }
55
+ async function digestRegularFile(path) {
56
+ let file;
57
+ try {
58
+ const before = await lstat(path);
59
+ if (before.isSymbolicLink() || !before.isFile() || before.size > EXECUTABLE_BYTE_LIMIT || (before.mode & 0o111) === 0)
60
+ return undefined;
61
+ file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
62
+ const opened = await file.stat();
63
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > EXECUTABLE_BYTE_LIMIT)
64
+ return undefined;
65
+ const fingerprint = fileFingerprint(path, opened);
66
+ const remembered = verifiedDigest(fingerprint);
67
+ if (remembered !== undefined)
68
+ return fileFingerprint(path, await lstat(path)) === fingerprint ? remembered : undefined;
69
+ const hash = createHash('sha256');
70
+ const chunk = Buffer.allocUnsafe(1_048_576);
71
+ let total = 0;
72
+ while (true) {
73
+ const { bytesRead } = await file.read(chunk, 0, Math.min(chunk.byteLength, EXECUTABLE_BYTE_LIMIT - total + 1), null);
74
+ if (bytesRead === 0)
75
+ break;
76
+ total += bytesRead;
77
+ if (total > EXECUTABLE_BYTE_LIMIT)
78
+ return undefined;
79
+ hash.update(chunk.subarray(0, bytesRead));
80
+ }
81
+ const after = await file.stat();
82
+ const linked = await lstat(path);
83
+ const sha256 = total === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs
84
+ && linked.dev === opened.dev && linked.ino === opened.ino && linked.size === opened.size && linked.mtimeMs === opened.mtimeMs
85
+ ? hash.digest('hex')
86
+ : undefined;
87
+ if (sha256 !== undefined && fileFingerprint(path, after) === fingerprint)
88
+ recordVerifiedDigest(fingerprint, sha256);
89
+ await file.close();
90
+ file = undefined;
91
+ return sha256;
92
+ }
93
+ catch {
94
+ // Any read failure means no digest; the caller reports the executable identity as unavailable.
95
+ return undefined;
96
+ }
97
+ finally {
98
+ try {
99
+ await file?.close();
100
+ }
101
+ catch { /* The caller receives unavailable; no descriptor authority survives. */ }
102
+ }
103
+ }
104
+ export async function resolveExecutablePath(path) {
105
+ if (typeof path !== 'string' || !isAbsolute(path) || normalize(path) !== path || path.includes('\0')) {
106
+ return unavailable('Executable path must be an exact absolute path');
107
+ }
108
+ let invocation;
109
+ try {
110
+ invocation = await lstat(path);
111
+ }
112
+ catch (error) {
113
+ return unavailable(`Executable is unavailable: ${path}: ${errorCause(error)}`);
114
+ }
115
+ if (!(invocation.isFile() || invocation.isSymbolicLink()) || invocation.isFile() && (invocation.mode & 0o111) === 0) {
116
+ return unavailable(`Executable is unavailable: ${path}`);
117
+ }
118
+ let canonicalPath;
119
+ try {
120
+ canonicalPath = await realpath(path);
121
+ }
122
+ catch (error) {
123
+ return unavailable(`Executable is unavailable: ${path}: ${errorCause(error)}`);
124
+ }
125
+ const sha256 = await digestRegularFile(canonicalPath);
126
+ if (sha256 === undefined)
127
+ return unavailable(`Executable identity is unavailable: ${path}`);
128
+ const value = resolvedExecutable({ canonicalPath, invocationPath: path, sha256, executionMode: 'verified-path' });
129
+ return value ? { state: 'available', value } : unavailable(`Executable identity is invalid: ${path}`);
130
+ }
131
+ export async function resolveExecutable(name, env) {
132
+ if (typeof name !== 'string' || name.length === 0 || name.includes('\0'))
133
+ return unavailable('Executable name is invalid');
134
+ if (isAbsolute(name))
135
+ return resolveExecutablePath(name);
136
+ if (basename(name) !== name)
137
+ return unavailable('Executable name must be absolute or a PATH entry name');
138
+ const selected = searchPath(env);
139
+ if (selected.kind === 'unreadable')
140
+ return unavailable('Executable search path is not readable');
141
+ const pathValue = selected.path;
142
+ if (typeof pathValue !== 'string' || pathValue.length === 0 || pathValue.includes('\0') || Buffer.byteLength(pathValue) > 65_536) {
143
+ return unavailable('Executable search path is missing or invalid');
144
+ }
145
+ const directories = pathValue.split(delimiter);
146
+ if (directories.length > PATH_ENTRY_LIMIT || directories.some(path => !isAbsolute(path) || normalize(path) !== path)) {
147
+ return unavailable('Executable search path must contain bounded absolute directories');
148
+ }
149
+ for (const directory of directories) {
150
+ const result = await resolveExecutablePath(join(directory, name));
151
+ if (result.state === 'available')
152
+ return result;
153
+ }
154
+ return unavailable(`Executable was not found in the supplied search path: ${name}`);
155
+ }
156
+ export async function resolveGitExecutable(env) {
157
+ return resolveExecutable('git', env);
158
+ }
159
+ export async function resolveDiskCapacityExecutable() {
160
+ return resolveExecutablePath(process.execPath);
161
+ }
162
+ export async function resolveDockerExecutable(env) {
163
+ return resolveExecutable('docker', env);
164
+ }
165
+ export async function resolvePathRemovalExecutable() {
166
+ return resolveExecutablePath('/bin/rm');
167
+ }
@@ -0,0 +1,5 @@
1
+ import { type ConsumedWorkApplicationAuthorities, type GeneratedApplicationProjection, type GeneratedEnvironmentCommandInput, type RefusalOutcome, type WorkApplicationAuthoritiesProjection } from './shared.js';
2
+ export declare function generatedApplicationProjection(value: unknown): GeneratedApplicationProjection | undefined;
3
+ export declare function projectWorkApplicationAuthorities(application: GeneratedApplicationProjection): WorkApplicationAuthoritiesProjection | undefined;
4
+ export declare function consumeWorkApplicationAuthorities(projection: WorkApplicationAuthoritiesProjection): ConsumedWorkApplicationAuthorities | undefined;
5
+ export declare function prepareWorkApplicationAuthorities(input: GeneratedEnvironmentCommandInput): Promise<WorkApplicationAuthoritiesProjection | RefusalOutcome>;