@b00dea/cockpit 0.0.0-stage → 0.1.1

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 (426) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE +21 -0
  3. package/README.md +25 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2644 -0
  6. package/dist/autonomy.d.ts +45 -0
  7. package/dist/autonomy.js +173 -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 +117 -0
  11. package/dist/browser/commands.js +453 -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 +118 -0
  17. package/dist/browser/runtime.js +186 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +561 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/canonical-json.d.ts +2 -0
  23. package/dist/canonical-json.js +5 -0
  24. package/dist/cli-guard.d.ts +5 -0
  25. package/dist/cli-guard.js +71 -0
  26. package/dist/cli-main.d.ts +176 -0
  27. package/dist/cli-main.js +1584 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.js +16 -0
  30. package/dist/command-table.d.ts +121 -0
  31. package/dist/command-table.js +664 -0
  32. package/dist/config/load.d.ts +183 -0
  33. package/dist/config/load.js +367 -0
  34. package/dist/config/sibling-toml.d.ts +48 -0
  35. package/dist/config/sibling-toml.js +160 -0
  36. package/dist/config/surface-tools-toml.d.ts +9 -0
  37. package/dist/config/surface-tools-toml.js +37 -0
  38. package/dist/config/unconfirmed-toml.d.ts +9 -0
  39. package/dist/config/unconfirmed-toml.js +29 -0
  40. package/dist/config/verified-write.d.ts +43 -0
  41. package/dist/config/verified-write.js +89 -0
  42. package/dist/diagnostics.d.ts +26 -0
  43. package/dist/diagnostics.js +344 -0
  44. package/dist/docs/check.d.ts +16 -0
  45. package/dist/docs/check.js +197 -0
  46. package/dist/docs/decision-record.d.ts +34 -0
  47. package/dist/docs/decision-record.js +135 -0
  48. package/dist/docs/decisions.d.ts +16 -0
  49. package/dist/docs/decisions.js +103 -0
  50. package/dist/docs/markdown.d.ts +36 -0
  51. package/dist/docs/markdown.js +88 -0
  52. package/dist/docs/references.d.ts +32 -0
  53. package/dist/docs/references.js +148 -0
  54. package/dist/emit.d.ts +22 -0
  55. package/dist/emit.js +43 -0
  56. package/dist/entrypoint-kinds.d.ts +3 -0
  57. package/dist/entrypoint-kinds.js +11 -0
  58. package/dist/environment/application/confirm.d.ts +35 -0
  59. package/dist/environment/application/confirm.js +122 -0
  60. package/dist/environment/application/destructive.d.ts +6 -0
  61. package/dist/environment/application/destructive.js +429 -0
  62. package/dist/environment/application/doctor.d.ts +94 -0
  63. package/dist/environment/application/doctor.js +247 -0
  64. package/dist/environment/application/guard.d.ts +49 -0
  65. package/dist/environment/application/guard.js +677 -0
  66. package/dist/environment/application/health.d.ts +143 -0
  67. package/dist/environment/application/health.js +385 -0
  68. package/dist/environment/application/lifecycle-context.d.ts +235 -0
  69. package/dist/environment/application/lifecycle-context.js +154 -0
  70. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  71. package/dist/environment/application/lifecycle-state.js +202 -0
  72. package/dist/environment/application/lifecycle.d.ts +18 -0
  73. package/dist/environment/application/lifecycle.js +737 -0
  74. package/dist/environment/application/logs.d.ts +45 -0
  75. package/dist/environment/application/logs.js +127 -0
  76. package/dist/environment/application/services.d.ts +60 -0
  77. package/dist/environment/application/services.js +89 -0
  78. package/dist/environment/application/setup.d.ts +129 -0
  79. package/dist/environment/application/setup.js +741 -0
  80. package/dist/environment/application/shared.d.ts +11 -0
  81. package/dist/environment/application/shared.js +61 -0
  82. package/dist/environment/application/siblings.d.ts +43 -0
  83. package/dist/environment/application/siblings.js +269 -0
  84. package/dist/environment/claims.d.ts +389 -0
  85. package/dist/environment/claims.js +1701 -0
  86. package/dist/environment/codex-hook-trust.d.ts +11 -0
  87. package/dist/environment/codex-hook-trust.js +185 -0
  88. package/dist/environment/compose-environment.d.ts +1 -0
  89. package/dist/environment/compose-environment.js +6 -0
  90. package/dist/environment/compose-ownership.d.ts +21 -0
  91. package/dist/environment/compose-ownership.js +117 -0
  92. package/dist/environment/compose-topology.d.ts +125 -0
  93. package/dist/environment/compose-topology.js +490 -0
  94. package/dist/environment/contract.d.ts +522 -0
  95. package/dist/environment/contract.js +794 -0
  96. package/dist/environment/declaration.d.ts +14 -0
  97. package/dist/environment/declaration.js +39 -0
  98. package/dist/environment/destructive-composition.d.ts +29 -0
  99. package/dist/environment/destructive-composition.js +333 -0
  100. package/dist/environment/docker-daemon.d.ts +30 -0
  101. package/dist/environment/docker-daemon.js +78 -0
  102. package/dist/environment/docker-endpoint.d.ts +10 -0
  103. package/dist/environment/docker-endpoint.js +19 -0
  104. package/dist/environment/env-schema-observation.d.ts +46 -0
  105. package/dist/environment/env-schema-observation.js +241 -0
  106. package/dist/environment/evidence.d.ts +136 -0
  107. package/dist/environment/evidence.js +437 -0
  108. package/dist/environment/executable.d.ts +38 -0
  109. package/dist/environment/executable.js +190 -0
  110. package/dist/environment/generated-application/authorities.d.ts +5 -0
  111. package/dist/environment/generated-application/authorities.js +61 -0
  112. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  113. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  114. package/dist/environment/generated-application/check.d.ts +13 -0
  115. package/dist/environment/generated-application/check.js +191 -0
  116. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  117. package/dist/environment/generated-application/custom-surface.js +289 -0
  118. package/dist/environment/generated-application/prepare.d.ts +29 -0
  119. package/dist/environment/generated-application/prepare.js +278 -0
  120. package/dist/environment/generated-application/publication.d.ts +33 -0
  121. package/dist/environment/generated-application/publication.js +547 -0
  122. package/dist/environment/generated-application/shared.d.ts +492 -0
  123. package/dist/environment/generated-application/shared.js +354 -0
  124. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  125. package/dist/environment/generated-application/surface-observation.js +69 -0
  126. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  127. package/dist/environment/generated-application/surface-outputs.js +400 -0
  128. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  129. package/dist/environment/generated-application/surfaces.js +704 -0
  130. package/dist/environment/generated-application/sync.d.ts +6 -0
  131. package/dist/environment/generated-application/sync.js +124 -0
  132. package/dist/environment/git-observation.d.ts +202 -0
  133. package/dist/environment/git-observation.js +553 -0
  134. package/dist/environment/health-composition.d.ts +12 -0
  135. package/dist/environment/health-composition.js +645 -0
  136. package/dist/environment/host-observation.d.ts +56 -0
  137. package/dist/environment/host-observation.js +52 -0
  138. package/dist/environment/identity.d.ts +48 -0
  139. package/dist/environment/identity.js +85 -0
  140. package/dist/environment/init-application.d.ts +333 -0
  141. package/dist/environment/init-application.js +726 -0
  142. package/dist/environment/install-policy-observation.d.ts +71 -0
  143. package/dist/environment/install-policy-observation.js +284 -0
  144. package/dist/environment/install-policy.d.ts +48 -0
  145. package/dist/environment/install-policy.js +96 -0
  146. package/dist/environment/instance.d.ts +33 -0
  147. package/dist/environment/instance.js +122 -0
  148. package/dist/environment/instruction-pointer.d.ts +19 -0
  149. package/dist/environment/instruction-pointer.js +144 -0
  150. package/dist/environment/lifecycle-composition.d.ts +31 -0
  151. package/dist/environment/lifecycle-composition.js +742 -0
  152. package/dist/environment/lifecycle-stage.d.ts +120 -0
  153. package/dist/environment/lifecycle-stage.js +408 -0
  154. package/dist/environment/lifecycle.d.ts +75 -0
  155. package/dist/environment/lifecycle.js +354 -0
  156. package/dist/environment/logs.d.ts +132 -0
  157. package/dist/environment/logs.js +519 -0
  158. package/dist/environment/map-limit.d.ts +2 -0
  159. package/dist/environment/map-limit.js +13 -0
  160. package/dist/environment/mcp-observation.d.ts +9 -0
  161. package/dist/environment/mcp-observation.js +46 -0
  162. package/dist/environment/observations.d.ts +46 -0
  163. package/dist/environment/observations.js +346 -0
  164. package/dist/environment/operator-application.d.ts +79 -0
  165. package/dist/environment/operator-application.js +197 -0
  166. package/dist/environment/ports.d.ts +182 -0
  167. package/dist/environment/ports.js +1205 -0
  168. package/dist/environment/posture-observation.d.ts +68 -0
  169. package/dist/environment/posture-observation.js +253 -0
  170. package/dist/environment/presence.d.ts +4 -0
  171. package/dist/environment/presence.js +26 -0
  172. package/dist/environment/read-composition.d.ts +32 -0
  173. package/dist/environment/read-composition.js +397 -0
  174. package/dist/environment/readiness-composition.d.ts +57 -0
  175. package/dist/environment/readiness-composition.js +151 -0
  176. package/dist/environment/reclaim.d.ts +370 -0
  177. package/dist/environment/reclaim.js +1940 -0
  178. package/dist/environment/repository-identity.d.ts +13 -0
  179. package/dist/environment/repository-identity.js +108 -0
  180. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  181. package/dist/environment/runtime-measurement-record.js +111 -0
  182. package/dist/environment/runtime-measurement.d.ts +27 -0
  183. package/dist/environment/runtime-measurement.js +224 -0
  184. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  185. package/dist/environment/runtime-probe-residue.js +148 -0
  186. package/dist/environment/runtime-probe.d.ts +72 -0
  187. package/dist/environment/runtime-probe.js +725 -0
  188. package/dist/environment/runtime.d.ts +551 -0
  189. package/dist/environment/runtime.js +2405 -0
  190. package/dist/environment/seed.d.ts +16 -0
  191. package/dist/environment/seed.js +43 -0
  192. package/dist/environment/selftest-application.d.ts +86 -0
  193. package/dist/environment/selftest-application.js +713 -0
  194. package/dist/environment/sensitive.d.ts +3 -0
  195. package/dist/environment/sensitive.js +48 -0
  196. package/dist/environment/served-checkout-grants.d.ts +53 -0
  197. package/dist/environment/served-checkout-grants.js +182 -0
  198. package/dist/environment/setup-observation.d.ts +62 -0
  199. package/dist/environment/setup-observation.js +1473 -0
  200. package/dist/environment/setup.d.ts +43 -0
  201. package/dist/environment/setup.js +176 -0
  202. package/dist/environment/sibling-checkouts.d.ts +119 -0
  203. package/dist/environment/sibling-checkouts.js +254 -0
  204. package/dist/environment/sibling-instructions.d.ts +33 -0
  205. package/dist/environment/sibling-instructions.js +224 -0
  206. package/dist/environment/sibling-observation.d.ts +27 -0
  207. package/dist/environment/sibling-observation.js +45 -0
  208. package/dist/environment/sibling-pull.d.ts +23 -0
  209. package/dist/environment/sibling-pull.js +95 -0
  210. package/dist/environment/sibling-registration.d.ts +45 -0
  211. package/dist/environment/sibling-registration.js +538 -0
  212. package/dist/environment/stack-inputs.d.ts +18 -0
  213. package/dist/environment/stack-inputs.js +210 -0
  214. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  215. package/dist/environment/stack-inventory-prune.js +129 -0
  216. package/dist/environment/stack-inventory-report.d.ts +44 -0
  217. package/dist/environment/stack-inventory-report.js +78 -0
  218. package/dist/environment/stack-inventory.d.ts +94 -0
  219. package/dist/environment/stack-inventory.js +418 -0
  220. package/dist/environment/state.d.ts +410 -0
  221. package/dist/environment/state.js +2153 -0
  222. package/dist/environment/templates.d.ts +140 -0
  223. package/dist/environment/templates.js +685 -0
  224. package/dist/environment/tool-registration.d.ts +9 -0
  225. package/dist/environment/tool-registration.js +92 -0
  226. package/dist/environment/upgrade-application.d.ts +19 -0
  227. package/dist/environment/upgrade-application.js +175 -0
  228. package/dist/environment-exec/execute.d.ts +105 -0
  229. package/dist/environment-exec/execute.js +1179 -0
  230. package/dist/environment-exec/libexec-program.d.ts +6 -0
  231. package/dist/environment-exec/libexec-program.js +22 -0
  232. package/dist/environment-exec/verified-digest.d.ts +4 -0
  233. package/dist/environment-exec/verified-digest.js +16 -0
  234. package/dist/exact-keys.d.ts +7 -0
  235. package/dist/exact-keys.js +15 -0
  236. package/dist/fix-catalogue.d.ts +32 -0
  237. package/dist/fix-catalogue.js +60 -0
  238. package/dist/freshness.d.ts +29 -0
  239. package/dist/freshness.js +75 -0
  240. package/dist/generated-environment/adopt.d.ts +16 -0
  241. package/dist/generated-environment/adopt.js +271 -0
  242. package/dist/generated-environment/command.d.ts +47 -0
  243. package/dist/generated-environment/command.js +75 -0
  244. package/dist/generated-environment/transaction.d.ts +186 -0
  245. package/dist/generated-environment/transaction.js +1770 -0
  246. package/dist/grading/evaluate.d.ts +71 -0
  247. package/dist/grading/evaluate.js +403 -0
  248. package/dist/grading/grade.d.ts +33 -0
  249. package/dist/grading/grade.js +57 -0
  250. package/dist/guards/answer-confirmation.d.ts +41 -0
  251. package/dist/guards/answer-confirmation.js +109 -0
  252. package/dist/guards/evaluator.d.ts +130 -0
  253. package/dist/guards/evaluator.js +1258 -0
  254. package/dist/guards/harness-adapter.d.ts +17 -0
  255. package/dist/guards/harness-adapter.js +57 -0
  256. package/dist/guards/host.d.ts +8 -0
  257. package/dist/guards/host.js +10 -0
  258. package/dist/guards/invocation-parser.d.ts +66 -0
  259. package/dist/guards/invocation-parser.js +595 -0
  260. package/dist/guards/local-identity.d.ts +44 -0
  261. package/dist/guards/local-identity.js +129 -0
  262. package/dist/guards/policy-change.d.ts +43 -0
  263. package/dist/guards/policy-change.js +159 -0
  264. package/dist/guards/policy-pin.d.ts +13 -0
  265. package/dist/guards/policy-pin.js +177 -0
  266. package/dist/guards/protected-locations.d.ts +62 -0
  267. package/dist/guards/protected-locations.js +461 -0
  268. package/dist/guards/read-only.d.ts +37 -0
  269. package/dist/guards/read-only.js +315 -0
  270. package/dist/init/command.d.ts +41 -0
  271. package/dist/init/command.js +330 -0
  272. package/dist/init/derive.d.ts +53 -0
  273. package/dist/init/derive.js +819 -0
  274. package/dist/init/draft.d.ts +140 -0
  275. package/dist/init/draft.js +1228 -0
  276. package/dist/init/render.d.ts +47 -0
  277. package/dist/init/render.js +104 -0
  278. package/dist/init/scaffold.d.ts +53 -0
  279. package/dist/init/scaffold.js +121 -0
  280. package/dist/invocation.d.ts +13 -0
  281. package/dist/invocation.js +20 -0
  282. package/dist/package-metadata.d.ts +9 -0
  283. package/dist/package-metadata.js +19 -0
  284. package/dist/readiness.d.ts +53 -0
  285. package/dist/readiness.js +88 -0
  286. package/dist/references/named.d.ts +24 -0
  287. package/dist/references/named.js +27 -0
  288. package/dist/refusal.d.ts +15 -0
  289. package/dist/refusal.js +7 -0
  290. package/dist/registry-derivation.d.ts +31 -0
  291. package/dist/registry-derivation.js +32 -0
  292. package/dist/registry-resolve/command.d.ts +47 -0
  293. package/dist/registry-resolve/command.js +226 -0
  294. package/dist/registry-resolve/registry.d.ts +68 -0
  295. package/dist/registry-resolve/registry.js +292 -0
  296. package/dist/registry-resolve/render-shell.d.ts +18 -0
  297. package/dist/registry-resolve/render-shell.js +36 -0
  298. package/dist/repair.d.ts +29 -0
  299. package/dist/repair.js +8 -0
  300. package/dist/repository-path.d.ts +9 -0
  301. package/dist/repository-path.js +16 -0
  302. package/dist/schema-json.d.ts +1 -0
  303. package/dist/schema-json.js +177 -0
  304. package/dist/schema.d.ts +1289 -0
  305. package/dist/schema.js +985 -0
  306. package/dist/setup/command.d.ts +4 -0
  307. package/dist/setup/command.js +4 -0
  308. package/dist/sha256.d.ts +1 -0
  309. package/dist/sha256.js +4 -0
  310. package/dist/surfaces/adoption.d.ts +41 -0
  311. package/dist/surfaces/adoption.js +217 -0
  312. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  313. package/dist/surfaces/consumer-instructions.js +43 -0
  314. package/dist/surfaces/contract.d.ts +42 -0
  315. package/dist/surfaces/contract.js +169 -0
  316. package/dist/surfaces/core-declarations.d.ts +24 -0
  317. package/dist/surfaces/core-declarations.js +79 -0
  318. package/dist/surfaces/development-markers.d.ts +1 -0
  319. package/dist/surfaces/development-markers.js +18 -0
  320. package/dist/surfaces/generator-adapter.d.ts +71 -0
  321. package/dist/surfaces/generator-adapter.js +809 -0
  322. package/dist/surfaces/harness-registry.d.ts +260 -0
  323. package/dist/surfaces/harness-registry.js +146 -0
  324. package/dist/surfaces/mcp.d.ts +145 -0
  325. package/dist/surfaces/mcp.js +349 -0
  326. package/dist/surfaces/merge.d.ts +30 -0
  327. package/dist/surfaces/merge.js +105 -0
  328. package/dist/surfaces/ownership.d.ts +190 -0
  329. package/dist/surfaces/ownership.js +795 -0
  330. package/dist/surfaces/projection.d.ts +136 -0
  331. package/dist/surfaces/projection.js +716 -0
  332. package/dist/surfaces/render.d.ts +79 -0
  333. package/dist/surfaces/render.js +636 -0
  334. package/dist/surfaces/restrictions.d.ts +130 -0
  335. package/dist/surfaces/restrictions.js +447 -0
  336. package/dist/surfaces/selftest.d.ts +162 -0
  337. package/dist/surfaces/selftest.js +393 -0
  338. package/dist/surfaces/skill-contract.d.ts +85 -0
  339. package/dist/surfaces/skill-contract.js +449 -0
  340. package/dist/surfaces/sync.d.ts +57 -0
  341. package/dist/surfaces/sync.js +164 -0
  342. package/dist/team-metadata.d.ts +41 -0
  343. package/dist/team-metadata.js +513 -0
  344. package/dist/testing/gates.d.ts +91 -0
  345. package/dist/testing/gates.js +182 -0
  346. package/dist/testing/hooks.d.ts +59 -0
  347. package/dist/testing/hooks.js +139 -0
  348. package/dist/testing/test-protection.d.ts +34 -0
  349. package/dist/testing/test-protection.js +137 -0
  350. package/dist/testing/verdict.d.ts +29 -0
  351. package/dist/testing/verdict.js +62 -0
  352. package/dist/testing/weakening.d.ts +15 -0
  353. package/dist/testing/weakening.js +30 -0
  354. package/dist/work-orders/application-closure.d.ts +7 -0
  355. package/dist/work-orders/application-closure.js +76 -0
  356. package/dist/work-orders/application-context.d.ts +162 -0
  357. package/dist/work-orders/application-context.js +137 -0
  358. package/dist/work-orders/application-decisions.d.ts +27 -0
  359. package/dist/work-orders/application-decisions.js +121 -0
  360. package/dist/work-orders/application-grading.d.ts +11 -0
  361. package/dist/work-orders/application-grading.js +212 -0
  362. package/dist/work-orders/application.d.ts +6 -0
  363. package/dist/work-orders/application.js +223 -0
  364. package/dist/work-orders/composition.d.ts +101 -0
  365. package/dist/work-orders/composition.js +1202 -0
  366. package/dist/work-orders/contract.d.ts +77 -0
  367. package/dist/work-orders/contract.js +140 -0
  368. package/dist/work-orders/decisions.d.ts +23 -0
  369. package/dist/work-orders/decisions.js +55 -0
  370. package/dist/work-orders/store.d.ts +207 -0
  371. package/dist/work-orders/store.js +291 -0
  372. package/docs/README.md +68 -0
  373. package/docs/architecture.md +136 -0
  374. package/docs/capability-matrix.md +58 -0
  375. package/docs/cli-reference.md +807 -0
  376. package/docs/command-list.md +62 -0
  377. package/docs/compose-patterns.md +99 -0
  378. package/docs/concepts.md +173 -0
  379. package/docs/configuration-essentials.md +177 -0
  380. package/docs/configuration-reference.md +636 -0
  381. package/docs/docs-check.md +76 -0
  382. package/docs/environment.md +948 -0
  383. package/docs/extending.md +43 -0
  384. package/docs/faq.md +113 -0
  385. package/docs/getting-started.md +692 -0
  386. package/docs/harness-surfaces.md +500 -0
  387. package/docs/joining-a-team.md +76 -0
  388. package/docs/release-policy.md +49 -0
  389. package/docs/safety-and-autonomy.md +463 -0
  390. package/docs/troubleshooting.md +382 -0
  391. package/docs/tutorial.md +171 -0
  392. package/docs/work-and-browser.md +420 -0
  393. package/libexec/browser-operation.cjs +270 -0
  394. package/libexec/fence-marker.cjs +101 -0
  395. package/libexec/fence-marker.d.cts +24 -0
  396. package/libexec/fenced-group-runner.cjs +77 -0
  397. package/libexec/fenced-supervisor.cjs +137 -0
  398. package/libexec/generated-filesystem.cjs +731 -0
  399. package/libexec/install-filesystem.cjs +225 -0
  400. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  401. package/libexec/rulesync-generator.cjs +743 -0
  402. package/libexec/surface-generator-worker.cjs +266 -0
  403. package/libexec/surface-output-reader.cjs +99 -0
  404. package/libexec/surface-scratch-cleanup.cjs +14 -0
  405. package/libexec/validate-harness-evidence.d.mts +183 -0
  406. package/libexec/validate-harness-evidence.mjs +1312 -0
  407. package/package.json +74 -4
  408. package/surfaces/README.md +33 -0
  409. package/surfaces/agents/evaluator.agent.md +76 -0
  410. package/surfaces/guards/bash-guard.sh +48 -0
  411. package/surfaces/guards/claude.adapter.json +25 -0
  412. package/surfaces/guards/codex.adapter.json +24 -0
  413. package/surfaces/guards/opencode.adapter.json +25 -0
  414. package/surfaces/references/browser.md +63 -0
  415. package/surfaces/references/test-data-and-personas.md +84 -0
  416. package/surfaces/references/testing-tiers.md +11 -0
  417. package/surfaces/skills/cockpit-browser.skill.md +59 -0
  418. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  419. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  420. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  421. package/surfaces/skills/cockpit-work.skill.md +34 -0
  422. package/surfaces/skills/collection.json +5 -0
  423. package/surfaces/skills/procedures/explain.md +66 -0
  424. package/surfaces/skills/procedures/fix.md +95 -0
  425. package/surfaces/skills/procedures/setup.md +452 -0
  426. package/surfaces/skills/procedures/work.md +134 -0
@@ -0,0 +1,1202 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {} from '../schema.js';
3
+ import { sha256Hex } from '../sha256.js';
4
+ import { closeSync, constants, existsSync, fstatSync, lstatSync, openSync, readSync, realpathSync, readdirSync, } from 'node:fs';
5
+ import { mkdir, open as openFile, rm } from 'node:fs/promises';
6
+ import { basename, delimiter, dirname, isAbsolute, join, parse as parsePath, relative, resolve, sep } from 'node:path';
7
+ import { isProxy } from 'node:util/types';
8
+ import { readBoundedFile } from '../bounded-read.js';
9
+ import { tmpdir } from 'node:os';
10
+ import { readRecordedPolicy } from '../autonomy.js';
11
+ import { defaultEvaluatorOrder, evaluate, locateEvaluators } from '../grading/evaluate.js';
12
+ import { gradingDocument, gradingSchema, readGradingAnswer } from '../grading/grade.js';
13
+ import { ENVIRONMENT_CODES, errorCause, hasErrorCode, INHERITED_TOOL_ENVIRONMENT, RefusalError } from '../environment/contract.js';
14
+ import { authenticatedExecutionOutcome, executeProbe } from '../environment-exec/execute.js';
15
+ import { issueBrowserRoutePolicy } from '../browser/commands.js';
16
+ import {} from '../browser/runtime.js';
17
+ import { measuredBrowserHarnessVersions, observeBrowserProjectPackage, renderBrowserSurfaces } from '../browser/surfaces.js';
18
+ import { isGuardSurfaceTarget } from '../surfaces/harness-registry.js';
19
+ import { cockpitCommand } from '../fix-catalogue.js';
20
+ import { stripDevelopmentMarkers } from '../surfaces/development-markers.js';
21
+ import { decodeLifecycleRecord } from '../environment/application/lifecycle.js';
22
+ import { selectLocalDockerDaemon } from '../environment/docker-daemon.js';
23
+ import { resolveDockerExecutable, resolveExecutablePath } from '../environment/executable.js';
24
+ import { discoverMachineStacks } from '../environment/reclaim.js';
25
+ import { consumeWorkApplicationAuthorities, prepareWorkApplicationAuthorities, projectWorkApplicationAuthorities, } from '../environment/generated-application/authorities.js';
26
+ import { prepareGeneratedEnvironmentCurrentness } from '../environment/generated-application/check.js';
27
+ import { createLifecycleCommandAdapters } from '../environment/lifecycle-composition.js';
28
+ import { createReadCommandAdapters } from '../environment/read-composition.js';
29
+ import { deriveEntrypoints, observeRetainedPortFamily } from '../environment/ports.js';
30
+ import { readState } from '../environment/state.js';
31
+ import { parseDiff } from '../testing/test-protection.js';
32
+ import { testFilePatterns, weakeningFindings } from '../testing/weakening.js';
33
+ import { runGate } from '../testing/gates.js';
34
+ import { readSiblingCheckouts, missingServedCheckout, servedPath, servedSiblings } from '../environment/sibling-checkouts.js';
35
+ import { splitRepositoryPath } from '../repository-path.js';
36
+ import { parseCriteriaFile, parseWorkOrder, scopeMatcher, WORK_ORDER_ID } from './contract.js';
37
+ import { createWorkStore, outputTail, WORK_RECORD_DIRECTORY } from './store.js';
38
+ const issuedCompositions = new WeakSet();
39
+ const MAX_DOCUMENT_BYTES = 8 * 1_048_576;
40
+ const MAX_REPOSITORY_PATHS = 4_096;
41
+ const MAX_REPOSITORY_FILE_BYTES = 8 * 1_048_576;
42
+ const MAX_REPOSITORY_AGGREGATE_BYTES = 64 * 1_048_576;
43
+ const MAX_DECISION_RECORDS = 4_096;
44
+ const MAX_DECISION_RECORD_BYTES = 262_144;
45
+ // What the grader is shown of one changed file, and of the whole change.
46
+ const MAX_DIFF_BYTES = 65_536;
47
+ const MAX_CHANGE_BYTES = 1_048_576;
48
+ const VERIFICATION_TIMEOUT_FIELD = 'work.verification_timeout_seconds';
49
+ const DEFAULT_EXECUTABLE_DIRECTORIES = Object.freeze(['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin']);
50
+ // Work-order coordination files change as the work is tracked, not as the product changes: the orders,
51
+ // cockpit's records of them and the recorded policy never make a gate's proof stale.
52
+ const COORDINATION_PATHS = new Set(['workorders/', `${WORK_RECORD_DIRECTORY}/`, '.cockpit/policy.json']);
53
+ // Nor is anything cockpit writes below .cockpit/ a change an order made.
54
+ const COCKPIT_WRITTEN = new Set([...COORDINATION_PATHS, '.cockpit/']);
55
+ function boundedName(value) {
56
+ return typeof value === 'string' && WORK_ORDER_ID.test(value);
57
+ }
58
+ function exactSource(source) {
59
+ try {
60
+ const bytes = readExactFile(source.path, source.byteLength);
61
+ return bytes.byteLength === source.byteLength && sha256Hex(bytes) === source.sha256;
62
+ }
63
+ catch {
64
+ return false;
65
+ } // A source that cannot be read back cannot be shown to be the admitted bytes; callers refuse it as changed.
66
+ }
67
+ function contained(root, relativePath, allowAbsent = false) {
68
+ if (typeof relativePath !== 'string' || !relativePath || isAbsolute(relativePath) || relativePath.includes('\0')
69
+ || relativePath.split(/[\\/]/).some(part => !part || part === '.' || part === '..'))
70
+ throw new RefusalError('path escapes its admitted root');
71
+ const target = resolve(root, relativePath);
72
+ if (target !== root && !target.startsWith(`${root}${sep}`))
73
+ throw new RefusalError('path escapes its admitted root');
74
+ let cursor = root;
75
+ for (const part of relativePath.split(/[\\/]/)) {
76
+ cursor = join(cursor, part);
77
+ if (!existsSync(cursor)) {
78
+ if (allowAbsent)
79
+ break;
80
+ throw new RefusalError('contained path is missing');
81
+ }
82
+ const metadata = lstatSync(cursor);
83
+ if (metadata.isSymbolicLink())
84
+ throw new RefusalError('contained path cannot traverse a symbolic link');
85
+ }
86
+ if (existsSync(target) && realpathSync(target) !== target)
87
+ throw new RefusalError('contained path changed identity');
88
+ return target;
89
+ }
90
+ function readAnchorsCurrent(anchors) {
91
+ try {
92
+ return anchors.every(anchor => {
93
+ const opened = fstatSync(anchor.descriptor, { bigint: true });
94
+ const named = lstatSync(anchor.path, { bigint: true });
95
+ return opened.isDirectory() && named.isDirectory() && !named.isSymbolicLink()
96
+ && opened.dev === anchor.dev && opened.ino === anchor.ino
97
+ && named.dev === anchor.dev && named.ino === anchor.ino
98
+ && (!anchor.trackContents || opened.mtimeNs === anchor.mtimeNs && opened.ctimeNs === anchor.ctimeNs
99
+ && named.mtimeNs === anchor.mtimeNs && named.ctimeNs === anchor.ctimeNs);
100
+ });
101
+ }
102
+ catch {
103
+ return false;
104
+ } // An anchor that can no longer be observed is not current; callers refuse the read as changed.
105
+ }
106
+ function anchoredReadPath(root, relativePath, allowAbsent = false, contentRoot = root) {
107
+ if (constants.O_DIRECTORY === undefined || constants.O_NOFOLLOW === undefined || constants.O_NONBLOCK === undefined
108
+ || !isAbsolute(root) || resolve(root) !== root || typeof relativePath !== 'string' || !relativePath
109
+ || isAbsolute(relativePath) || relativePath.includes('\0')
110
+ || relativePath.split(/[\\/]/).some(part => !part || part === '.' || part === '..')) {
111
+ throw new RefusalError('path escapes its admitted root');
112
+ }
113
+ const target = resolve(root, relativePath);
114
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
115
+ if (target === root || !target.startsWith(rootPrefix))
116
+ throw new RefusalError('path escapes its admitted root');
117
+ const directories = [root];
118
+ let cursor = root;
119
+ for (const part of relativePath.split(/[\\/]/).slice(0, -1)) {
120
+ cursor = join(cursor, part);
121
+ directories.push(cursor);
122
+ }
123
+ const anchors = [];
124
+ try {
125
+ for (const path of directories) {
126
+ const trackContents = path === contentRoot || path.startsWith(contentRoot.endsWith(sep) ? contentRoot : `${contentRoot}${sep}`);
127
+ let named;
128
+ try {
129
+ named = lstatSync(path, { bigint: true });
130
+ }
131
+ catch (error) {
132
+ if (allowAbsent && hasErrorCode(error, 'ENOENT')) {
133
+ if (!readAnchorsCurrent(anchors))
134
+ throw new RefusalError('contained file ancestor changed while observing an absent path');
135
+ closeReadAnchors(anchors);
136
+ return undefined;
137
+ }
138
+ throw error;
139
+ }
140
+ if (!named.isDirectory() || named.isSymbolicLink())
141
+ throw new RefusalError('contained file ancestor is unsafe');
142
+ const descriptor = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
143
+ try {
144
+ const opened = fstatSync(descriptor, { bigint: true });
145
+ if (!opened.isDirectory() || opened.dev !== named.dev || opened.ino !== named.ino
146
+ || trackContents && (opened.mtimeNs !== named.mtimeNs || opened.ctimeNs !== named.ctimeNs)) {
147
+ throw new RefusalError('contained file ancestor changed while anchoring');
148
+ }
149
+ anchors.push({ path, descriptor, trackContents, dev: opened.dev, ino: opened.ino,
150
+ mtimeNs: opened.mtimeNs, ctimeNs: opened.ctimeNs });
151
+ }
152
+ catch (error) {
153
+ closeSync(descriptor);
154
+ throw error;
155
+ }
156
+ }
157
+ if (!readAnchorsCurrent(anchors))
158
+ throw new RefusalError('contained file ancestor changed before leaf admission');
159
+ return { target, anchors };
160
+ }
161
+ catch (error) {
162
+ for (const anchor of anchors.reverse())
163
+ closeSync(anchor.descriptor);
164
+ throw error;
165
+ }
166
+ }
167
+ function anchoredAbsoluteReadPath(path) {
168
+ if (!isAbsolute(path) || resolve(path) !== path)
169
+ throw new RefusalError('file path is not canonical');
170
+ const root = parsePath(path).root;
171
+ // Shared outer directories may gain unrelated entries; the owning directory and leaf stay content-bound.
172
+ const anchored = anchoredReadPath(root, relative(root, path), false, dirname(path));
173
+ if (!anchored)
174
+ throw new RefusalError('file is missing');
175
+ return anchored;
176
+ }
177
+ function closeReadAnchors(anchors) {
178
+ let failure;
179
+ for (const anchor of [...anchors].reverse())
180
+ try {
181
+ closeSync(anchor.descriptor);
182
+ }
183
+ catch (error) {
184
+ failure ??= error;
185
+ }
186
+ if (failure !== undefined)
187
+ throw failure;
188
+ }
189
+ // The leaf is read by the shared no-follow helper; a second link would let an agent-writable name
190
+ // alias a file outside the admitted root, so only a single-link file is accepted.
191
+ function readAnchoredLeaf(anchored, maximum, what) {
192
+ try {
193
+ const read = readBoundedFile(anchored.target, maximum);
194
+ if (read.kind !== 'complete')
195
+ throw new RefusalError(`${what} exceeds its ${maximum}-byte read bound`);
196
+ if (lstatSync(anchored.target).nlink !== 1)
197
+ throw new RefusalError(`${what} has more than one link`);
198
+ if (!readAnchorsCurrent(anchored.anchors))
199
+ throw new RefusalError(`${what} ancestor changed during read`);
200
+ return read.bytes;
201
+ }
202
+ finally {
203
+ closeReadAnchors(anchored.anchors);
204
+ }
205
+ }
206
+ function readBounded(root, relativePath, maximum = MAX_DOCUMENT_BYTES) {
207
+ const anchored = anchoredReadPath(root, relativePath);
208
+ if (!anchored)
209
+ throw new RefusalError('contained file is missing');
210
+ return readAnchoredLeaf(anchored, maximum, 'contained file');
211
+ }
212
+ // Node has no openat, so a write cannot name its file relative to a directory descriptor. Every ancestor is
213
+ // anchored by descriptor instead, the leaf is opened without following a link, and nothing is written until
214
+ // the opened file is shown to be the single-link regular file at the confined name, under ancestors that are
215
+ // still the anchored directories: one swapped for a link while the file was opened fails that check.
216
+ async function openContainedForWrite(root, relativePath, flags) {
217
+ // No ancestor's contents are bound: the write itself changes the directory that holds the file.
218
+ const anchored = anchoredReadPath(root, relativePath, false, join(root, relativePath));
219
+ if (!anchored)
220
+ throw new RefusalError('contained path is missing');
221
+ try {
222
+ const handle = await openFile(anchored.target, flags | constants.O_WRONLY | constants.O_NOFOLLOW, 0o644);
223
+ try {
224
+ const opened = await handle.stat({ bigint: true });
225
+ const named = lstatSync(anchored.target, { bigint: true, throwIfNoEntry: false });
226
+ if (!opened.isFile() || opened.nlink !== 1n || opened.dev !== named?.dev || opened.ino !== named.ino || !readAnchorsCurrent(anchored.anchors)) {
227
+ throw new RefusalError('contained file or an ancestor changed while it was opened for writing; nothing was written');
228
+ }
229
+ return handle;
230
+ }
231
+ catch (error) {
232
+ await handle.close();
233
+ throw error;
234
+ }
235
+ }
236
+ finally {
237
+ closeReadAnchors(anchored.anchors);
238
+ }
239
+ }
240
+ function readExactFile(path, maximum) {
241
+ const anchored = anchoredAbsoluteReadPath(path);
242
+ try {
243
+ if (realpathSync(path) !== path)
244
+ throw new RefusalError('file path is not canonical');
245
+ }
246
+ catch (error) {
247
+ closeReadAnchors(anchored.anchors);
248
+ throw error;
249
+ }
250
+ return readAnchoredLeaf(anchored, maximum, 'file');
251
+ }
252
+ async function executable(directories, name) {
253
+ let unavailableReason = `Executable ${name} is unavailable`;
254
+ for (const directory of directories) {
255
+ if (!isAbsolute(directory))
256
+ continue;
257
+ const candidate = join(directory, name);
258
+ const resolved = await resolveExecutablePath(candidate);
259
+ if (resolved.state !== 'available') {
260
+ unavailableReason = resolved.reason;
261
+ continue;
262
+ }
263
+ let bytes;
264
+ try {
265
+ bytes = lstatSync(resolved.value.canonicalPath).size;
266
+ }
267
+ catch (error) {
268
+ unavailableReason = `Executable ${name} is unavailable: ${errorCause(error)}`;
269
+ continue;
270
+ }
271
+ return { state: 'available', value: {
272
+ path: resolved.value.canonicalPath, sha256: resolved.value.sha256, bytes,
273
+ } };
274
+ }
275
+ return { state: 'unavailable', reason: unavailableReason };
276
+ }
277
+ async function executeBounded(root, argv, directories = DEFAULT_EXECUTABLE_DIRECTORIES, timeoutMs = 30_000, mode = { kind: 'probe' }) {
278
+ if (!Array.isArray(argv) || !argv.length || argv.length > 128 || argv.some(value => typeof value !== 'string' || !value || Buffer.byteLength(value) > 4_096)) {
279
+ return { kind: 'error', reason: 'Execution requires bounded argv' };
280
+ }
281
+ if (!Array.isArray(directories) || !directories.length || directories.length > 64
282
+ || directories.some(directory => typeof directory !== 'string' || !isAbsolute(directory) || resolve(directory) !== directory
283
+ || directory.includes('\0') || directory.includes(delimiter) || Buffer.byteLength(directory) > 4_096)
284
+ || Buffer.byteLength(directories.join(delimiter)) > 65_536) {
285
+ return { kind: 'error', reason: 'Execution requires a bounded executable search path' };
286
+ }
287
+ const resolution = await (isAbsolute(argv[0]) ? executable([dirname(argv[0])], basename(argv[0])) : executable(directories, argv[0]));
288
+ if (resolution.state !== 'available')
289
+ return { kind: 'unavailable', reason: resolution.reason };
290
+ const resolved = resolution.value;
291
+ const consumedFile = { path: resolved.path, sha256: resolved.sha256, maxBytes: resolved.bytes,
292
+ argvIndexes: [0], executable: true, executionMode: 'verified-path' };
293
+ const filesystemCheck = { path: root, expected: 'directory' };
294
+ const stepArgv = [resolved.path, ...argv.slice(1)];
295
+ const step = { id: `work-${randomUUID()}`, argv: stepArgv, argv0: resolved.path,
296
+ // macOS's /usr/bin/git looks up a temporary directory when TMPDIR is unset, and inside Codex's
297
+ // workspace-write sandbox that lookup fails and git warns on stderr, which fails a probe that
298
+ // requires silence; a probe therefore names the temporary directory Node already resolved.
299
+ cwd: root, env: { ...(mode.kind === 'declared' ? mode.env : { TMPDIR: tmpdir() }), PATH: directories.join(delimiter) }, timeoutMs,
300
+ spawnNotAfter: new Date(Date.now() + timeoutMs).toISOString(), stdoutLimitBytes: 65_536, stderrLimitBytes: 65_536,
301
+ ...(mode.kind === 'declared' ? { outputOverflow: 'retain-tail' } : {}),
302
+ paths: [], ports: [], labels: { owner: 'work-application' }, expected: [],
303
+ consumedFiles: [consumedFile], filesystemChecks: [filesystemCheck], compensation: [] };
304
+ const result = authenticatedExecutionOutcome(await executeProbe(step), step);
305
+ if (!result)
306
+ return { kind: 'error', reason: `the outcome of ${argv[0]} could not be authenticated as the issued step` };
307
+ if (result.kind === 'refusal' && result.code === ENVIRONMENT_CODES.EXECUTION_TIMEOUT) {
308
+ return { kind: 'error', reason: `timed out after ${timeoutMs / 1_000} seconds${mode.kind === 'declared' ? `; raise ${mode.timeoutField}` : ''}` };
309
+ }
310
+ if (result.kind === 'refusal' && result.code === ENVIRONMENT_CODES.EXECUTION_SPAWN_FAILED)
311
+ return { kind: 'unavailable', reason: `${argv[0]} could not be started` };
312
+ // A non-zero exit carries a receipt only when the process started and its termination was confirmed.
313
+ const exited = result.kind === 'complete' || result.code === ENVIRONMENT_CODES.EXECUTION_STEP_FAILED && result.receipt !== undefined;
314
+ if (!exited)
315
+ return { kind: 'error', reason: `${argv[0]} did not run to completion: ${result.code}` };
316
+ const truncated = result.execution.stdoutObservedBytes !== result.execution.stdoutRetainedBytes
317
+ || result.execution.stderrObservedBytes !== result.execution.stderrRetainedBytes;
318
+ if (truncated && mode.kind === 'probe')
319
+ return { kind: 'error', reason: `${argv[0]} output exceeded the 64 KiB probe capture` };
320
+ // A probe parses its output, so it refuses invalid UTF-8; a declared command's output is evidence to read.
321
+ const decode = (bytes) => new TextDecoder('utf-8', { fatal: mode.kind === 'probe' }).decode(bytes);
322
+ let stdout;
323
+ let stderr;
324
+ try {
325
+ stdout = decode(result.stdout);
326
+ stderr = decode(result.stderr);
327
+ }
328
+ catch (error) {
329
+ return { kind: 'error', reason: `${argv[0]} returned invalid UTF-8: ${errorCause(error)}` };
330
+ }
331
+ return { kind: 'executed', exitCode: result.execution.exitCode, stdout, stderr, truncated,
332
+ observedBytes: result.execution.stdoutObservedBytes + result.execution.stderrObservedBytes };
333
+ }
334
+ function admitRepositoryPath(root, identity, path, sensitivePaths, forbiddenTargets = new Set()) {
335
+ const parts = path.split('/');
336
+ if (!path || Buffer.byteLength(path) > 4_096 || path.includes('\0') || isAbsolute(path)
337
+ || parts.some(part => !part || part === '.' || part === '..'))
338
+ throw new RefusalError(`repository subject ${identity} contains an unsafe path: ${path}`);
339
+ const foldedPath = path.toLowerCase();
340
+ if ([...sensitivePaths].some(candidate => candidate.toLowerCase() === foldedPath)) {
341
+ throw new RefusalError(`repository subject ${identity} refuses declared environment instance: ${path}`);
342
+ }
343
+ const target = resolve(root, path);
344
+ const foldedTarget = target.toLowerCase();
345
+ if ([...forbiddenTargets].some(candidate => candidate.toLowerCase() === foldedTarget)) {
346
+ throw new RefusalError(`repository subject ${identity} refuses browser authentication state: ${path}`);
347
+ }
348
+ const basename = parts.at(-1) ?? '';
349
+ const safeEnvironmentTemplate = /^\.env(?:\.[A-Za-z0-9_-]+)*\.(?:example|schema|template|sample)$/i.test(basename);
350
+ const universalCredentialShape = parts.some(part => ['.ssh', '.aws', '.gnupg', '.secrets', '.npmrc', '.netrc', '.pypirc'].includes(part.toLowerCase())
351
+ || /^(?:id_rsa|id_dsa|id_ecdsa|id_ed25519)(?:\.pub)?$/i.test(part) || /\.(?:key|pem|p12|pfx)$/i.test(part));
352
+ const environmentCredentialParent = parts.slice(0, -1).some(part => part.toLowerCase() === '.env' || part.toLowerCase().startsWith('.env.'));
353
+ const finalEnvironmentCredential = basename.toLowerCase() === '.env' || basename.toLowerCase().startsWith('.env.');
354
+ if (universalCredentialShape || environmentCredentialParent || finalEnvironmentCredential && !safeEnvironmentTemplate) {
355
+ throw new RefusalError(`repository subject ${identity} refuses credential-shaped path: ${path}`);
356
+ }
357
+ return target;
358
+ }
359
+ function repositoryPathDigest(root, identity, path, aggregate, sensitivePaths, forbiddenTargets = new Set()) {
360
+ admitRepositoryPath(root, identity, path, sensitivePaths, forbiddenTargets);
361
+ const anchored = anchoredReadPath(root, path, true);
362
+ if (!anchored)
363
+ return null;
364
+ try {
365
+ let stat;
366
+ try {
367
+ stat = lstatSync(anchored.target);
368
+ }
369
+ catch (error) {
370
+ if (hasErrorCode(error, 'ENOENT')) {
371
+ if (!readAnchorsCurrent(anchored.anchors))
372
+ throw new RefusalError('repository subject ancestor changed while observing an absent file');
373
+ return null;
374
+ }
375
+ throw error;
376
+ }
377
+ if (stat.isSymbolicLink())
378
+ throw new RefusalError(`repository subject ${identity} refuses a symbolic link: ${path}`);
379
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > MAX_REPOSITORY_FILE_BYTES
380
+ || aggregate.bytes + stat.size > MAX_REPOSITORY_AGGREGATE_BYTES)
381
+ throw new RefusalError('repository subject file exceeds its authenticated bound');
382
+ const descriptor = openSync(anchored.target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
383
+ try {
384
+ const opened = fstatSync(descriptor);
385
+ if (opened.dev !== stat.dev || opened.ino !== stat.ino || opened.nlink !== 1 || opened.size !== stat.size
386
+ || opened.mtimeMs !== stat.mtimeMs || opened.ctimeMs !== stat.ctimeMs)
387
+ throw new RefusalError('repository subject file changed before hashing');
388
+ const hash = createHash('sha256');
389
+ const chunk = Buffer.allocUnsafe(1_048_576);
390
+ let total = 0;
391
+ while (total < opened.size) {
392
+ const count = readSync(descriptor, chunk, 0, Math.min(chunk.byteLength, opened.size - total), null);
393
+ if (count <= 0)
394
+ throw new RefusalError('repository subject file ended before its observed size');
395
+ total += count;
396
+ hash.update(chunk.subarray(0, count));
397
+ }
398
+ const after = fstatSync(descriptor);
399
+ const linked = lstatSync(anchored.target);
400
+ if (total !== opened.size || after.dev !== opened.dev || after.ino !== opened.ino || after.nlink !== 1 || after.size !== opened.size
401
+ || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs
402
+ || linked.dev !== opened.dev || linked.ino !== opened.ino || linked.nlink !== 1 || linked.size !== opened.size
403
+ || linked.mtimeMs !== opened.mtimeMs || linked.ctimeMs !== opened.ctimeMs
404
+ || !readAnchorsCurrent(anchored.anchors))
405
+ throw new RefusalError('repository subject file or ancestor changed while hashing');
406
+ aggregate.bytes += total;
407
+ return hash.digest('hex');
408
+ }
409
+ finally {
410
+ closeSync(descriptor);
411
+ }
412
+ }
413
+ finally {
414
+ closeReadAnchors(anchored.anchors);
415
+ }
416
+ }
417
+ function statusPath(record, spaces) {
418
+ let offset = 0;
419
+ for (let index = 0; index < spaces; index += 1) {
420
+ offset = record.indexOf(' ', offset);
421
+ if (offset < 0)
422
+ throw new RefusalError('repository status record is malformed');
423
+ offset += 1;
424
+ }
425
+ const path = record.slice(offset);
426
+ if (!path)
427
+ throw new RefusalError('repository status path is missing');
428
+ return path;
429
+ }
430
+ function excludedRepositoryPath(path, excluded) {
431
+ return [...excluded].some(candidate => candidate.endsWith('/')
432
+ ? path === candidate.slice(0, -1) || path.startsWith(candidate)
433
+ : path === candidate);
434
+ }
435
+ function repositoryEnvironmentInstancePaths(repositoryRoot, siblings) {
436
+ const paths = new Set();
437
+ for (const sibling of siblings) {
438
+ const prefix = relative(repositoryRoot, sibling.root);
439
+ if (isAbsolute(prefix) || prefix === '..' || prefix.startsWith(`..${sep}`))
440
+ continue;
441
+ for (const file of sibling.environmentFiles)
442
+ paths.add(prefix ? join(prefix, file.instance) : file.instance);
443
+ }
444
+ return paths;
445
+ }
446
+ // Why a probe that must exit 0 silently did not, in words a person can act on: the first line of
447
+ // what it wrote to stderr is usually the whole story (a sandbox denial, a missing repository).
448
+ function probeFailure(command, observed) {
449
+ if (observed.kind !== 'executed')
450
+ return `${command} did not run: ${observed.reason}`;
451
+ const stderr = observed.stderr.trim().split('\n', 1)[0]?.slice(0, 240);
452
+ if (observed.exitCode !== 0)
453
+ return `${command} exited ${observed.exitCode}${stderr ? `: ${stderr}` : ''}`;
454
+ if (stderr)
455
+ return `${command} wrote to stderr: ${stderr}`;
456
+ return observed.truncated ? `${command} output exceeded the probe capture` : `${command} printed no newline-terminated answer`;
457
+ }
458
+ async function observeRepositorySubject(repository, excluded, directories, sensitivePaths = new Set(), forbiddenTargets = new Set()) {
459
+ const prefixResult = await executeBounded(repository.root, ['git', 'rev-parse', '--show-prefix'], directories);
460
+ if (prefixResult.kind !== 'executed' || prefixResult.exitCode !== 0 || prefixResult.stderr || prefixResult.truncated
461
+ || !prefixResult.stdout.endsWith('\n')) {
462
+ throw new RefusalError(`repository subject ${repository.identity} root is unavailable: ${probeFailure('git rev-parse --show-prefix', prefixResult)}`);
463
+ }
464
+ const prefix = prefixResult.stdout.slice(0, -1);
465
+ if (prefix && (!prefix.endsWith('/') || isAbsolute(prefix) || prefix.slice(0, -1).split('/').some(part => !part || part === '.' || part === '..'))) {
466
+ throw new RefusalError(`repository subject ${repository.identity} root prefix is unsafe`);
467
+ }
468
+ const localPath = (path) => {
469
+ if (!prefix)
470
+ return path;
471
+ if (!path.startsWith(prefix))
472
+ throw new RefusalError(`repository subject ${repository.identity} escaped its declared root: ${path}`);
473
+ return path.slice(prefix.length);
474
+ };
475
+ const status = await executeBounded(repository.root, ['git', 'status', '--porcelain=v2', '--branch', '-z', '--untracked-files=all', '--', '.'], directories);
476
+ if (status.kind !== 'executed' || status.exitCode !== 0 || status.stderr || status.truncated) {
477
+ throw new RefusalError(`repository subject ${repository.identity} is unavailable: ${probeFailure('git status', status)}`);
478
+ }
479
+ const parseStatus = (stdout) => {
480
+ const records = stdout.split('\0');
481
+ if (records.at(-1) !== '')
482
+ throw new RefusalError('repository status is not NUL terminated');
483
+ records.pop();
484
+ let revision;
485
+ const changes = [];
486
+ const normalizedRecords = [];
487
+ const admittedPaths = new Set();
488
+ for (let index = 0; index < records.length; index += 1) {
489
+ const record = records[index];
490
+ if (record.startsWith('# branch.oid ')) {
491
+ if (revision !== undefined)
492
+ throw new RefusalError('repository status contains duplicate HEAD identity');
493
+ revision = record.slice('# branch.oid '.length);
494
+ normalizedRecords.push(record);
495
+ continue;
496
+ }
497
+ if (record.startsWith('# ')) {
498
+ normalizedRecords.push(record);
499
+ continue;
500
+ }
501
+ let kind;
502
+ let metadata;
503
+ let paths;
504
+ let pairedRecord;
505
+ if (record.startsWith('1 ')) {
506
+ kind = 'ordinary';
507
+ paths = [statusPath(record, 8)];
508
+ metadata = record.slice(0, record.length - paths[0].length);
509
+ }
510
+ else if (record.startsWith('2 ')) {
511
+ kind = 'rename';
512
+ const destination = statusPath(record, 9);
513
+ const source = records[++index];
514
+ if (source === undefined)
515
+ throw new RefusalError('repository rename source is missing');
516
+ paths = [destination, source];
517
+ metadata = record.slice(0, record.length - destination.length);
518
+ pairedRecord = source;
519
+ }
520
+ else if (record.startsWith('u ')) {
521
+ kind = 'unmerged';
522
+ paths = [statusPath(record, 10)];
523
+ metadata = record.slice(0, record.length - paths[0].length);
524
+ }
525
+ else if (record.startsWith('? ')) {
526
+ kind = 'untracked';
527
+ paths = [record.slice(2)];
528
+ metadata = '? ';
529
+ }
530
+ else
531
+ throw new RefusalError('repository status contains an unsupported record');
532
+ const included = paths.map(localPath).filter(path => !excludedRepositoryPath(path, excluded));
533
+ if (!included.length)
534
+ continue;
535
+ if (included.some(path => admittedPaths.has(path)))
536
+ throw new RefusalError('repository subject contains duplicate paths');
537
+ for (const path of included)
538
+ admittedPaths.add(path);
539
+ if (admittedPaths.size > MAX_REPOSITORY_PATHS)
540
+ throw new RefusalError('repository subject exceeds its path bound');
541
+ const change = { kind, metadata, paths: included };
542
+ changes.push(change);
543
+ normalizedRecords.push(record);
544
+ if (pairedRecord !== undefined)
545
+ normalizedRecords.push(pairedRecord);
546
+ }
547
+ // A repository made by `sibling add --create` has no HEAD until its first commit, and a work
548
+ // order binds every repository's revision, so name where that commit is missing.
549
+ if (revision === '(initial)') {
550
+ throw new RefusalError(`repository ${repository.identity} at ${repository.root} has no commits yet; fix: commit once in ${repository.root}`);
551
+ }
552
+ if (!revision || !/^(?=[a-f0-9]*[1-9a-f])(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(revision)) {
553
+ throw new RefusalError(`repository revision is unavailable for ${repository.identity} at ${repository.root}`);
554
+ }
555
+ changes.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
556
+ return { revision, changes, normalizedRecords };
557
+ };
558
+ const observedStatus = parseStatus(status.stdout);
559
+ const aggregate = { bytes: 0 };
560
+ const changes = observedStatus.changes.map(change => ({ ...change,
561
+ paths: change.paths.map(path => ({ path,
562
+ sha256: repositoryPathDigest(repository.root, repository.identity, path, aggregate, sensitivePaths, forbiddenTargets) })),
563
+ }));
564
+ const confirmed = await executeBounded(repository.root, ['git', 'status', '--porcelain=v2', '--branch', '-z', '--untracked-files=all', '--', '.'], directories);
565
+ const confirmedPrefix = await executeBounded(repository.root, ['git', 'rev-parse', '--show-prefix'], directories);
566
+ if (confirmed.kind !== 'executed' || confirmed.exitCode !== 0 || confirmed.stderr || confirmed.truncated
567
+ || confirmedPrefix.kind !== 'executed' || confirmedPrefix.exitCode !== 0 || confirmedPrefix.stdout !== prefixResult.stdout
568
+ || confirmedPrefix.stderr || confirmedPrefix.truncated) {
569
+ throw new RefusalError(`repository subject ${repository.identity} changed during observation`);
570
+ }
571
+ const confirmedStatus = parseStatus(confirmed.stdout);
572
+ if (JSON.stringify(confirmedStatus) !== JSON.stringify(observedStatus)) {
573
+ throw new RefusalError(`repository subject ${repository.identity} changed during observation`);
574
+ }
575
+ const confirmationAggregate = { bytes: 0 };
576
+ for (const change of changes)
577
+ for (const path of change.paths) {
578
+ if (repositoryPathDigest(repository.root, repository.identity, path.path, confirmationAggregate, sensitivePaths, forbiddenTargets) !== path.sha256) {
579
+ throw new RefusalError(`repository subject ${repository.identity} changed during hashing`);
580
+ }
581
+ }
582
+ return { path: repository.identity, revision: observedStatus.revision,
583
+ worktreeDigest: sha256Hex(JSON.stringify({ revision: observedStatus.revision, changes })) };
584
+ }
585
+ export function isWorkApplicationComposition(value) {
586
+ return value !== null && typeof value === 'object' && issuedCompositions.has(value);
587
+ }
588
+ function createWorkApplicationComposition(config, authorities, served) {
589
+ if (!config || isProxy(config) || !boundedName(config.cockpitName) || !isAbsolute(config.cockpitRoot)
590
+ || realpathSync(config.cockpitRoot) !== config.cockpitRoot || dirname(config.source.path) !== config.cockpitRoot
591
+ || typeof config.siblingsRoot !== 'string' || !config.siblingsRoot || config.siblingsRoot.includes('\0')
592
+ || !Array.isArray(config.siblings) || config.siblings.some(row => !boundedName(row?.slug) || typeof row?.dir !== 'string'
593
+ || isAbsolute(row.dir) || row.dir !== '.' && row.dir.split(/[\\/]/).some((part) => !part || part === '.' || part === '..'))
594
+ || config.siblings.some(row => !isAbsolute(served.get(row.slug)?.served ?? ''))
595
+ || !authorities || isProxy(authorities))
596
+ throw new RefusalError('work application composition requires admitted source-bound configuration');
597
+ const root = config.cockpitRoot;
598
+ const siblingsRoot = resolve(root, config.siblingsRoot);
599
+ if (realpathSync(siblingsRoot) !== siblingsRoot)
600
+ throw new RefusalError('work application composition requires a canonical siblings root');
601
+ const directories = authorities.executableDirectories ?? DEFAULT_EXECUTABLE_DIRECTORIES;
602
+ const browserAuthenticationTargets = new Set((config.browser?.authentication.states ?? []).map(row => resolve(root, row.path)));
603
+ // The cockpit's own repository is `.`; a sibling is named by its slug, and its paths read `<slug>:path`.
604
+ // A sibling is read where it is served, which `cockpit use` may have moved to a worktree, so a grade
605
+ // judges the code the stack runs. One served at the cockpit root is that same repository, under the cockpit's key.
606
+ const servedRoot = (slug) => servedPath(served, slug);
607
+ const siblingRoots = config.siblings.map(row => ({ root: servedRoot(row.slug), environmentFiles: row.environmentFiles }));
608
+ const repositories = [{ key: '.', identity: config.cockpitName, root }, ...config.siblings.flatMap(row => servedRoot(row.slug) === root ? []
609
+ : [{ key: row.slug, identity: `siblings/${row.slug}`, root: servedRoot(row.slug) }])]
610
+ .map(repository => ({ ...repository, sensitivePaths: repositoryEnvironmentInstancePaths(repository.root, siblingRoots) }));
611
+ const keyOf = (key) => config.siblings.some(row => row.slug === key && servedRoot(row.slug) === root) ? '.' : key;
612
+ const qualified = (key, path) => key === '.' ? path : `${key}:${path}`;
613
+ const located = (reference) => {
614
+ const { slug, path } = splitRepositoryPath(reference);
615
+ const key = slug === undefined ? '.' : keyOf(slug);
616
+ const repository = repositories.find(row => row.key === key);
617
+ if (!repository)
618
+ throw new RefusalError(`${reference} names no declared sibling ${key}`);
619
+ admitRepositoryPath(repository.root, repository.identity, path, repository.sensitivePaths, browserAuthenticationTargets);
620
+ return { repository, path };
621
+ };
622
+ const readSource = (reference) => {
623
+ const { repository, path } = located(reference);
624
+ try {
625
+ return readBounded(repository.root, path, MAX_DOCUMENT_BYTES);
626
+ }
627
+ catch (error) {
628
+ if (hasErrorCode(error, 'ENOENT', 'ENOTDIR'))
629
+ throw new RefusalError(`${reference} does not exist`);
630
+ throw error;
631
+ }
632
+ };
633
+ const readDocsFile = async (reference) => {
634
+ const { repository, path } = located(reference);
635
+ try {
636
+ return readBounded(repository.root, path, MAX_DECISION_RECORD_BYTES).toString('utf8');
637
+ }
638
+ catch (error) {
639
+ if (hasErrorCode(error, 'ENOENT', 'ENOTDIR'))
640
+ return undefined;
641
+ throw error;
642
+ }
643
+ };
644
+ const decisionRecords = async () => {
645
+ const declared = config.docs?.decisions;
646
+ if (!declared)
647
+ return new Map();
648
+ const { repository, path } = located(declared);
649
+ let names;
650
+ try {
651
+ names = readdirSync(contained(repository.root, path, true));
652
+ }
653
+ catch (error) {
654
+ if (hasErrorCode(error, 'ENOENT'))
655
+ return new Map();
656
+ throw error;
657
+ }
658
+ const records = names.filter(name => name.endsWith('.md') && lstatSync(join(repository.root, path, name)).isFile()).sort();
659
+ if (records.length > MAX_DECISION_RECORDS)
660
+ throw new RefusalError(`[docs] decisions ${declared} holds more than ${MAX_DECISION_RECORDS} records, more than cockpit reads`);
661
+ return new Map(records.map(name => [`${declared}/${name}`, readBounded(repository.root, `${path}/${name}`, MAX_DECISION_RECORD_BYTES).toString('utf8')]));
662
+ };
663
+ // The only files cockpit writes outside .cockpit/: a decision record it scaffolds or marks superseded, and the index line.
664
+ // Each is written through a descriptor opened by openContainedForWrite; a replacement is written in place, as a
665
+ // rename would name its target by path again.
666
+ const writeContained = async (repository, path, flags, text, replace = false) => {
667
+ const handle = await openContainedForWrite(repository.root, path, flags);
668
+ try {
669
+ if (replace)
670
+ await handle.truncate(0);
671
+ await handle.writeFile(text);
672
+ }
673
+ finally {
674
+ await handle.close();
675
+ }
676
+ };
677
+ // A write that fails part way puts back what it changed, so a rerun does not number a second record or mark the old one twice.
678
+ const writeDecisionRecords = async (writes, indexLine) => {
679
+ const undo = [];
680
+ try {
681
+ for (const { reference, text, create } of writes) {
682
+ const { repository, path } = located(reference);
683
+ await mkdir(dirname(contained(repository.root, path, true)), { recursive: true });
684
+ const target = contained(repository.root, path, true);
685
+ if (create) {
686
+ try {
687
+ await writeContained(repository, path, constants.O_CREAT | constants.O_EXCL, text);
688
+ }
689
+ catch (error) {
690
+ if (hasErrorCode(error, 'EEXIST'))
691
+ throw new RefusalError(`${reference} already exists; rerun work decide, which takes the next free name`);
692
+ throw error;
693
+ }
694
+ undo.push(() => rm(target, { force: true }));
695
+ continue;
696
+ }
697
+ const previous = readBounded(repository.root, path, MAX_DECISION_RECORD_BYTES).toString('utf8');
698
+ // Queued before the write: an in-place write that fails after truncating has already changed the record.
699
+ undo.push(() => writeContained(repository, path, 0, previous, true));
700
+ await writeContained(repository, path, 0, text, true);
701
+ }
702
+ if (indexLine === undefined)
703
+ return;
704
+ if (!config.docs?.decisionsIndex)
705
+ throw new RefusalError('an index line was given but [docs] decisions_index is not declared');
706
+ const index = located(config.docs.decisionsIndex);
707
+ const current = readBounded(index.repository.root, index.path, MAX_DECISION_RECORD_BYTES).toString('utf8');
708
+ await writeContained(index.repository, index.path, constants.O_APPEND, `${current === '' || current.endsWith('\n') ? '' : '\n'}${indexLine}\n`);
709
+ }
710
+ catch (error) {
711
+ // Every step is tried, so one that fails does not leave the files after it changed.
712
+ const failed = [];
713
+ for (const step of undo.reverse()) {
714
+ try {
715
+ await step();
716
+ }
717
+ catch (undoError) {
718
+ failed.push(errorCause(undoError));
719
+ }
720
+ }
721
+ if (failed.length)
722
+ throw new RefusalError(`${errorCause(error)}; putting back the decision records also failed (${failed.join('; ')}); `
723
+ + 'compare the [docs] decisions directory with git diff and restore what changed with git restore');
724
+ throw error;
725
+ }
726
+ };
727
+ const git = async (repository, args) => {
728
+ const observed = await executeBounded(repository.root, ['git', ...args], directories);
729
+ if (observed.kind !== 'executed' || observed.exitCode !== 0 || observed.truncated)
730
+ throw new RefusalError(`${repository.identity}: ${probeFailure(`git ${args.join(' ')}`, observed)}`);
731
+ return observed.stdout;
732
+ };
733
+ let evaluatorAuthority;
734
+ const resolveEvaluator = async () => {
735
+ evaluatorAuthority ??= authorities.evaluator ? Promise.resolve(authorities.evaluator)
736
+ : authorities.resolveEvaluator ? authorities.resolveEvaluator() : Promise.reject(new RefusalError('Authenticated evaluator surface is unavailable'));
737
+ try {
738
+ return await evaluatorAuthority;
739
+ }
740
+ catch (error) {
741
+ evaluatorAuthority = undefined;
742
+ throw error;
743
+ }
744
+ };
745
+ let browserAuthority;
746
+ const resolveBrowser = async () => {
747
+ browserAuthority ??= authorities.browser ? Promise.resolve(authorities.browser)
748
+ : authorities.resolveBrowser ? authorities.resolveBrowser() : Promise.reject(new RefusalError('browser application projection is unavailable'));
749
+ try {
750
+ return await browserAuthority;
751
+ }
752
+ catch (error) {
753
+ browserAuthority = undefined;
754
+ throw error;
755
+ }
756
+ };
757
+ const browser = async (kind, role, route) => {
758
+ if (!config.browser)
759
+ throw new RefusalError('browser application projection is unavailable');
760
+ const authority = await resolveBrowser();
761
+ return { runtime: authority.runtime, client: authority.client, policy: authority.policy, kind,
762
+ ...(authority.state ? { state: authority.state } : {}), ...(role ? { role } : {}), ...(route ? { route } : {}) };
763
+ };
764
+ const gate = async (request) => {
765
+ if (!authorities.testing || !config.testing)
766
+ throw new RefusalError('testing gate projection is unavailable');
767
+ const declared = authorities.testing.registry.gates.find(row => row.id === request.gate);
768
+ const stages = request.mode === 'full' ? declared?.stages : undefined;
769
+ const selected = stages ?? declared?.stages ?? [];
770
+ const needsStack = selected.some(id => authorities.testing.registry.stages.find(stage => stage.id === id)?.requiresStack);
771
+ const runtime = needsStack ? await authorities.testing.runtime?.() : undefined;
772
+ const stack = needsStack ? runtime?.stack ?? { state: 'unavailable', reason: 'authenticated runtime observation is unavailable' } : undefined;
773
+ const dependencies = runtime?.evidence
774
+ ? { ...authorities.testing.dependencies, runtime: runtime.evidence }
775
+ : authorities.testing.dependencies;
776
+ return { registry: authorities.testing.registry, request: { gate: request.gate, ...(stages ? { stages } : {}),
777
+ ...(request.select ? { select: request.select } : {}), staged: request.mode === 'staged', ...(stack ? { stack } : {}) }, dependencies };
778
+ };
779
+ const orderIds = async () => {
780
+ let names;
781
+ try {
782
+ names = readdirSync(contained(root, 'workorders', true));
783
+ }
784
+ catch (error) {
785
+ if (hasErrorCode(error, 'ENOENT'))
786
+ return [];
787
+ throw error;
788
+ }
789
+ return names.filter(name => name.endsWith('.md') && WORK_ORDER_ID.test(name.slice(0, -3))).map(name => name.slice(0, -3)).sort();
790
+ };
791
+ const readOrder = async (id) => {
792
+ let bytes;
793
+ try {
794
+ bytes = readBounded(root, `workorders/${id}.md`, 65_536);
795
+ }
796
+ catch (error) {
797
+ return { ok: false, problems: [`workorders/${id}.md cannot be read: ${errorCause(error)}`] };
798
+ }
799
+ return parseWorkOrder(bytes, id);
800
+ };
801
+ const runCheck = async (criterion, check) => {
802
+ if (Array.isArray(check)) {
803
+ // A check is the product's own test command, so it runs like a product test stage.
804
+ const observed = await executeBounded(root, check, directories, config.verificationTimeoutSeconds * 1_000, { kind: 'declared', env: authorities.declaredEnvironment ?? {}, timeoutField: VERIFICATION_TIMEOUT_FIELD });
805
+ return observed.kind === 'executed'
806
+ ? { criterion, kind: 'argv', passed: observed.exitCode === 0, outcome: `exit ${observed.exitCode}${observed.truncated ? ' (output truncated to its end)' : ''}`,
807
+ output: outputTail(`${observed.stdout}${observed.stderr}`) }
808
+ : { criterion, kind: 'argv', passed: false, outcome: `did not run: ${observed.reason}`.slice(0, 256), output: '' };
809
+ }
810
+ try {
811
+ const prepared = await gate({ operation: 'test-run', gate: check.gate, mode: 'canonical',
812
+ ...check.select ? { select: { kind: check.select.slice(0, check.select.indexOf(':')), value: check.select.slice(check.select.indexOf(':') + 1) } } : {} });
813
+ const run = await runGate(prepared.registry, prepared.request, prepared.dependencies);
814
+ return { criterion, kind: 'gate', passed: run.satisfiesGate, outcome: `gate ${check.gate}: ${run.verdict}`.slice(0, 256), output: outputTail(run.reasons.join('\n') || run.verdict) };
815
+ }
816
+ catch (error) {
817
+ return { criterion, kind: 'gate', passed: false, outcome: `gate ${check.gate} did not run`, output: outputTail(errorCause(error)) };
818
+ }
819
+ };
820
+ const heads = async () => Object.fromEntries(await Promise.all(repositories.map(async (repository) => {
821
+ const head = (await git(repository, ['rev-parse', '--verify', 'HEAD'])).trim();
822
+ if (!/^[a-f0-9]{40,64}$/.test(head))
823
+ throw new RefusalError(`${repository.identity} at ${repository.root} has no commit yet; commit once there`);
824
+ return [repository.key, head];
825
+ })));
826
+ const untrackedFiles = async () => new Map(await Promise.all(repositories.map(async (repository) => [repository.key, (await git(repository, ['ls-files', '--others', '--exclude-standard', '-z'])).split('\0').filter(Boolean)])));
827
+ // What changed since the base: committed, staged, unstaged and untracked files, each with its diff
828
+ // or new content. A credential-shaped or declared-instance path is listed but its content is withheld.
829
+ const changesWith = async (base, untrackedPaths) => {
830
+ const files = [];
831
+ let budget = MAX_CHANGE_BYTES;
832
+ // What sync generates is cockpit's, not the order's: the paths the ownership manifest lists.
833
+ const generated = new Set();
834
+ try {
835
+ const manifest = JSON.parse(readBounded(root, '.cockpit/generated-environment-ownership.json', MAX_DOCUMENT_BYTES).toString('utf8'));
836
+ if (Array.isArray(manifest.entries))
837
+ for (const entry of manifest.entries)
838
+ if (typeof entry?.path === 'string')
839
+ generated.add(entry.path);
840
+ }
841
+ catch (error) {
842
+ if (error instanceof SyntaxError)
843
+ throw new RefusalError('.cockpit/generated-environment-ownership.json is not JSON; run npx --no cockpit sync --config <your cockpit.toml>');
844
+ if (!hasErrorCode(error, 'ENOENT'))
845
+ throw error;
846
+ }
847
+ for (const repository of repositories) {
848
+ const from = base[repository.key] ?? 'HEAD';
849
+ const tracked = (await git(repository, ['diff', '--name-status', '-z', '--no-renames', from, '--'])).split('\0').filter(Boolean);
850
+ const rows = [];
851
+ for (let index = 0; index + 1 < tracked.length; index += 2)
852
+ rows.push([tracked[index], tracked[index + 1], false]);
853
+ for (const path of untrackedPaths.get(repository.key))
854
+ rows.push(['A', path, true]);
855
+ for (const [status, path, untracked] of rows) {
856
+ if (repository.key === '.' && (excludedRepositoryPath(path, COCKPIT_WRITTEN) || generated.has(path)))
857
+ continue;
858
+ let diff;
859
+ try {
860
+ admitRepositoryPath(repository.root, repository.identity, path, repository.sensitivePaths, browserAuthenticationTargets);
861
+ const observed = untracked
862
+ ? { kind: 'executed', stdout: readBounded(repository.root, path, MAX_DIFF_BYTES).toString('utf8') }
863
+ : await executeBounded(repository.root, ['git', 'diff', '--no-ext-diff', from, '--', path], directories, 30_000, { kind: 'declared', env: {}, timeoutField: VERIFICATION_TIMEOUT_FIELD });
864
+ diff = observed.kind === 'executed' ? observed.stdout : `(diff unavailable: ${observed.reason})`;
865
+ }
866
+ catch (error) {
867
+ if (!(error instanceof RefusalError))
868
+ throw error;
869
+ diff = `(content withheld: ${error.message})`;
870
+ }
871
+ const bounded = Buffer.byteLength(diff) > Math.min(MAX_DIFF_BYTES, budget) ? `${Buffer.from(diff).subarray(0, Math.min(MAX_DIFF_BYTES, budget)).toString('utf8')}\n(diff cut at its bound)` : diff;
872
+ budget = Math.max(0, budget - Buffer.byteLength(bounded));
873
+ files.push({ path: qualified(repository.key, path), status, diff: bounded });
874
+ }
875
+ }
876
+ return files;
877
+ };
878
+ // Test weakening since the base, per checkout: committed, staged and unstaged edits of tracked files,
879
+ // and new untracked test files, which a diff against the base does not list.
880
+ const weakeningWith = async (base, untracked) => {
881
+ const patterns = testFilePatterns(config.testing?.testFiles);
882
+ const isTest = scopeMatcher(patterns.tests);
883
+ const perRepository = await Promise.all(repositories.map(async (repository) => {
884
+ const from = base[repository.key] ?? 'HEAD';
885
+ const diff = await git(repository, ['-c', 'core.quotePath=true', 'diff', '--no-ext-diff', '--no-textconv', '--no-color', '--text', '--find-renames', '--unified=0', from, '--']);
886
+ const files = parseDiff(diff);
887
+ for (const path of untracked.get(repository.key)) {
888
+ if (!isTest(path))
889
+ continue;
890
+ let content;
891
+ try {
892
+ admitRepositoryPath(repository.root, repository.identity, path, repository.sensitivePaths, browserAuthenticationTargets);
893
+ content = readBounded(repository.root, path, MAX_DIFF_BYTES).toString('utf8');
894
+ }
895
+ catch (error) {
896
+ if (error instanceof RefusalError)
897
+ continue;
898
+ throw error;
899
+ }
900
+ files.push({ old: '/dev/null', next: path, hunk: true, removed: false, removedLines: [], renamed: false, deleted: false, added: content.split('\n') });
901
+ }
902
+ return weakeningFindings(files, patterns).map(row => ({ ...row, repository: repository.key }));
903
+ }));
904
+ return { patterns, findings: perRepository.flat() };
905
+ };
906
+ const testWeakening = async (base) => weakeningWith(base, await untrackedFiles());
907
+ const review = async (base) => {
908
+ const untracked = await untrackedFiles();
909
+ const [changed, weakening] = await Promise.all([changesWith(base, untracked), weakeningWith(base, untracked)]);
910
+ return { changes: changed, weakening };
911
+ };
912
+ const gradeIndependently = async (subject) => {
913
+ let evaluator;
914
+ try {
915
+ evaluator = await resolveEvaluator();
916
+ }
917
+ catch (error) {
918
+ return { refusal: { code: 'EVALUATOR_UNAVAILABLE', message: errorCause(error), fix: `run ${cockpitCommand('sync', { config: config.source.path })}` } };
919
+ }
920
+ if (evaluator.restriction !== 'instructed' || sha256Hex(evaluator.bytes) !== evaluator.sha256) {
921
+ return { refusal: { code: 'EVALUATOR_UNAVAILABLE', message: 'the shipped evaluator instructions could not be authenticated', fix: `run ${cockpitCommand('sync', { config: config.source.path })}` } };
922
+ }
923
+ // The shipped instructions carry notes to cockpit's maintainers that are not addressed to the grader.
924
+ const result = await evaluate({ chain: evaluator.chain, role: 'grader',
925
+ document: gradingDocument(stripDevelopmentMarkers(evaluator.bytes), root, subject), schema: gradingSchema(subject.criteria),
926
+ repository: root, timeoutMs: config.evaluatorTimeoutSeconds * 1_000, timeoutField: 'work.evaluator_timeout_seconds',
927
+ executableDirectories: evaluator.executableDirectories ?? directories, environment: evaluator.environment ?? authorities.env });
928
+ if (result.kind === 'refusal')
929
+ return { refusal: { code: result.code, message: result.message, fix: result.fix } };
930
+ const read = readGradingAnswer(result.value, subject);
931
+ if ('problem' in read)
932
+ return { refusal: { code: ENVIRONMENT_CODES.EVALUATOR_ANSWER_INVALID, message: read.problem, fix: 'rerun the command; if the answer is refused again, list another evaluator first in work.evaluators' } };
933
+ return { criteria: read.criteria, evaluator: { tool: result.tool, fallbacks: result.fallbacks } };
934
+ };
935
+ const containers = async () => {
936
+ const env = authorities.env;
937
+ const docker = await resolveDockerExecutable(env);
938
+ if (docker.state !== 'available')
939
+ return { state: 'no-docker', reason: docker.reason };
940
+ const daemon = await selectLocalDockerDaemon(env, 30_000, executeProbe, { command: 'work recover',
941
+ localOnly: 'a lock holder can be looked for only among the containers on this machine' });
942
+ if (daemon.kind === 'refusal')
943
+ return { state: 'unavailable', reason: daemon.diagnostic.message };
944
+ const listed = await discoverMachineStacks({ executable: docker.value, context: daemon.dockerContext, ...env.DOCKER_HOST === undefined ? {} : { host: env.DOCKER_HOST },
945
+ cwd: root, env: { PATH: env.PATH ?? '' }, timeoutMs: 30_000 }, executeProbe);
946
+ if (!listed)
947
+ return { state: 'unavailable', reason: `the containers cockpit labels could not be listed on Docker context ${daemon.dockerContext}; start Docker, or select the local context that runs cockpit stacks` };
948
+ return { state: 'listed', context: daemon.dockerContext, containers: listed.resources.filter(row => row.kind === 'container').map(({ id, name, running }) => ({ id, name, running })) };
949
+ };
950
+ const composition = {
951
+ policy: config.policy, caps: { wip: config.wipCap, attempts: config.attemptCap }, env: authorities.env,
952
+ store: createWorkStore(root), recordedPolicy: () => readRecordedPolicy(root), orderIds, readOrder,
953
+ readCriteriaFile: async (path) => parseCriteriaFile(readBounded(root, path, 65_536)),
954
+ sourceDigests: async (order) => Object.fromEntries(order.criteria.flatMap(row => row.source ? [row.source] : [])
955
+ .map(source => [source, sha256Hex(readSource(source))])),
956
+ heads, runCheck, evaluate: gradeIndependently, testWeakening, review, browser, gate,
957
+ ...config.docs ? { docs: config.docs } : {}, decisionRecords, readDocsFile, writeDecisionRecords, containers,
958
+ };
959
+ issuedCompositions.add(composition);
960
+ return composition;
961
+ }
962
+ function sameSource(left, right) {
963
+ return left.path === right.path && left.byteLength === right.byteLength && left.sha256 === right.sha256;
964
+ }
965
+ function captureConfiguredInput(value) {
966
+ try {
967
+ if (!value || typeof value !== 'object' || Array.isArray(value) || isProxy(value))
968
+ return undefined;
969
+ const descriptors = Object.getOwnPropertyDescriptors(value);
970
+ if (Reflect.ownKeys(descriptors).some(key => typeof key !== 'string')
971
+ || Object.keys(descriptors).sort().join('\0') !== ['configPath', 'env'].join('\0')
972
+ || Object.values(descriptors).some(row => !('value' in row)))
973
+ return undefined;
974
+ const configPath = descriptors.configPath.value;
975
+ const rawEnvironment = descriptors.env.value;
976
+ if (typeof configPath !== 'string' || !isAbsolute(configPath) || resolve(configPath) !== configPath
977
+ || !rawEnvironment || typeof rawEnvironment !== 'object' || Array.isArray(rawEnvironment) || isProxy(rawEnvironment))
978
+ return undefined;
979
+ const environmentDescriptors = Object.getOwnPropertyDescriptors(rawEnvironment);
980
+ if (Reflect.ownKeys(environmentDescriptors).some(key => typeof key !== 'string')
981
+ || Object.values(environmentDescriptors).some(row => !('value' in row)
982
+ || row.value !== undefined && typeof row.value !== 'string'))
983
+ return undefined;
984
+ return { configPath, env: Object.fromEntries(Object.entries(environmentDescriptors).map(([key, row]) => [key, row.value])) };
985
+ }
986
+ catch {
987
+ return undefined;
988
+ } // A throwing accessor or proxy trap on caller-supplied input is malformed input the caller refuses.
989
+ }
990
+ function declaredCommandEnvironment(environment) {
991
+ return Object.fromEntries(INHERITED_TOOL_ENVIRONMENT.flatMap(name => {
992
+ const value = environment[name];
993
+ return typeof value === 'string' && value.length > 0 && !value.includes('\0') && Buffer.byteLength(value) <= 4_096 ? [[name, value]] : [];
994
+ }));
995
+ }
996
+ function productGateDependencies(root, source, repositories, stageEnvironment, executableDirectories) {
997
+ return {
998
+ subject: async () => {
999
+ if (!exactSource(source))
1000
+ throw new RefusalError('configuration source changed during product gate');
1001
+ const observed = [];
1002
+ for (const repository of repositories) {
1003
+ observed.push(await observeRepositorySubject(repository, repository.root === root ? COORDINATION_PATHS : new Set(), executableDirectories ?? DEFAULT_EXECUTABLE_DIRECTORIES, repository.sensitivePaths, repository.forbiddenTargets));
1004
+ }
1005
+ return sha256Hex(JSON.stringify({ source: sha256Hex(readBounded(root, relative(root, source.path))), repositories: observed }));
1006
+ },
1007
+ runProductTool: async (invocation) => {
1008
+ const observed = await executeBounded(root, invocation.command, executableDirectories ?? DEFAULT_EXECUTABLE_DIRECTORIES, invocation.timeoutSeconds * 1_000, { kind: 'declared', env: stageEnvironment, timeoutField: 'testing.stages[].timeout_seconds' });
1009
+ if (observed.kind !== 'executed')
1010
+ return { kind: 'unavailable', reason: observed.reason };
1011
+ return { kind: 'executed', exitCode: observed.exitCode, signal: null, outputBytes: observed.observedBytes ?? 0,
1012
+ assertion: observed.exitCode === 0 ? 'passed' : 'failed' };
1013
+ },
1014
+ };
1015
+ }
1016
+ function executableDirectoriesFromEnvironment(environment) {
1017
+ const candidates = (environment.PATH ?? '').split(delimiter).filter(Boolean);
1018
+ if (!candidates.length || candidates.length > 64)
1019
+ throw new RefusalError('bounded executable search path is unavailable');
1020
+ const directories = [];
1021
+ for (const candidate of candidates) {
1022
+ if (!isAbsolute(candidate) || resolve(candidate) !== candidate)
1023
+ continue;
1024
+ try {
1025
+ const canonical = realpathSync(candidate);
1026
+ if (canonical === candidate && lstatSync(candidate).isDirectory() && !directories.includes(candidate))
1027
+ directories.push(candidate);
1028
+ }
1029
+ catch (error) {
1030
+ // A PATH entry that is absent or unreachable holds nothing a shell would run, so it is skipped as a shell skips it.
1031
+ if (hasErrorCode(error, 'ENOENT', 'ENOTDIR', 'EACCES', 'ELOOP'))
1032
+ continue;
1033
+ throw error;
1034
+ }
1035
+ }
1036
+ if (!directories.length)
1037
+ throw new RefusalError('canonical executable search directories are unavailable');
1038
+ return directories;
1039
+ }
1040
+ export async function createConfiguredWorkApplicationComposition(input, options = {}) {
1041
+ const captured = captureConfiguredInput(input);
1042
+ if (!captured)
1043
+ return { kind: 'refusal', reason: 'Configured work application requires a closed canonical config path and environment' };
1044
+ try {
1045
+ const projection = await prepareWorkApplicationAuthorities({ configPath: captured.configPath });
1046
+ if (projection.kind !== 'work-application-authorities') {
1047
+ return { kind: 'refusal', reason: projection.diagnostics.map(row => `${row.code}: ${row.message}`).join('; ') };
1048
+ }
1049
+ const authority = consumeWorkApplicationAuthorities(projection);
1050
+ if (!authority || !exactSource(authority.workEnvironment.source)) {
1051
+ return { kind: 'refusal', reason: 'Current generated work application authority is unavailable' };
1052
+ }
1053
+ const config = authority.workEnvironment;
1054
+ if (config.source.path !== captured.configPath || config.cockpitRoot !== dirname(captured.configPath)) {
1055
+ return { kind: 'refusal', reason: 'Generated work application authority does not match the selected configuration' };
1056
+ }
1057
+ const executableDirectories = executableDirectoriesFromEnvironment(captured.env);
1058
+ const declaredEnvironment = declaredCommandEnvironment(captured.env);
1059
+ const resolveEvaluator = async () => ({ path: authority.evaluator.path, bytes: authority.evaluator.bytes,
1060
+ sha256: authority.evaluator.sha256, restriction: authority.evaluator.restriction, executableDirectories,
1061
+ chain: await locateEvaluators(options.evaluator ? [options.evaluator] : config.evaluatorsDeclared ? config.evaluators : defaultEvaluatorOrder(config.evaluators, captured.env), executableDirectories), environment: captured.env });
1062
+ const resolveBrowser = async () => {
1063
+ if (!config.browser)
1064
+ throw new RefusalError('browser application projection is unavailable');
1065
+ const current = await prepareGeneratedEnvironmentCurrentness({ configPath: captured.configPath });
1066
+ if (current.outcome.kind !== 'complete' || current.outcome.result.dryRun) {
1067
+ throw new RefusalError('browser requires a current generated publication and active retained lease');
1068
+ }
1069
+ const projected = projectWorkApplicationAuthorities(current.outcome.result.application);
1070
+ const currentAuthority = projected && consumeWorkApplicationAuthorities(projected);
1071
+ const browserAuthority = currentAuthority?.browser;
1072
+ if (!browserAuthority || currentAuthority.workEnvironment.source.sha256 !== config.source.sha256
1073
+ || !exactSource(currentAuthority.workEnvironment.source))
1074
+ throw new RefusalError('current browser application authority is unavailable');
1075
+ if (captured.env.HOME !== process.env.HOME || captured.env.XDG_STATE_HOME !== process.env.XDG_STATE_HOME) {
1076
+ throw new RefusalError('browser state environment differs from canonical generated currentness');
1077
+ }
1078
+ const lifecycle = createLifecycleCommandAdapters({ configPath: captured.configPath, env: captured.env,
1079
+ sessionId: 'cockpit-work-application-read-only', timeoutMs: 30_000 }, executeProbe);
1080
+ const loaded = await lifecycle.load({ configPath: captured.configPath });
1081
+ if (loaded.kind !== 'complete' || !sameSource(loaded.result.source, config.source)
1082
+ || loaded.result.namespace.paths.lifecycle(loaded.result.stackId).path
1083
+ !== browserAuthority.namespace.paths.lifecycle(loaded.result.stackId).path)
1084
+ throw new RefusalError('authenticated browser lifecycle authority is unavailable');
1085
+ const stored = await readState(browserAuthority.namespace.paths.lifecycle(loaded.result.stackId));
1086
+ if (stored.kind !== 'complete')
1087
+ throw new RefusalError('browser lifecycle state is unavailable');
1088
+ const record = decodeLifecycleRecord(stored.value, loaded.result);
1089
+ if (!record)
1090
+ throw new RefusalError('browser lifecycle state is malformed');
1091
+ const retained = await observeRetainedPortFamily({ namespace: browserAuthority.namespace, stackId: loaded.result.stackId,
1092
+ holderId: record.value.holderId, checkoutId: loaded.result.checkoutId, config: loaded.result.ports });
1093
+ if (retained.kind !== 'complete')
1094
+ throw new RefusalError('retained application ports are unavailable');
1095
+ const entrypoints = deriveEntrypoints({ lease: retained.lease, declarations: loaded.result.entrypointDerivatives });
1096
+ if (entrypoints.kind !== 'complete')
1097
+ throw new RefusalError('declared application routes are unavailable');
1098
+ const runtime = browserAuthority.runtime;
1099
+ const mcp = browserAuthority.mcp;
1100
+ const connection = runtime.connection;
1101
+ if (connection.kind === 'remote' && mcp.args.filter(argument => argument === connection.endpoint).length !== 1) {
1102
+ throw new RefusalError('browser MCP declaration does not uniquely bind its current endpoint');
1103
+ }
1104
+ const origins = entrypoints.values.filter(row => row.kind === 'origin');
1105
+ if (origins.length !== 1)
1106
+ throw new RefusalError('browser application requires exactly one declared HTTP origin');
1107
+ const originUrl = new URL(origins[0].value);
1108
+ const declaredNames = new Set(loaded.result.entrypointDerivatives.map(row => row.name));
1109
+ const routeNames = new Map();
1110
+ const routesByUrl = new Map();
1111
+ for (const row of entrypoints.values) {
1112
+ if (!declaredNames.has(row.name))
1113
+ continue;
1114
+ let url;
1115
+ try {
1116
+ url = new URL(row.value);
1117
+ }
1118
+ catch {
1119
+ continue;
1120
+ } // A value that is not a URL is no browser route; an empty route set is refused below.
1121
+ if (url.origin !== originUrl.origin)
1122
+ continue;
1123
+ const prior = routeNames.get(row.name);
1124
+ if (prior !== undefined && prior !== url.href)
1125
+ throw new RefusalError('browser application routes are missing or ambiguous');
1126
+ routeNames.set(row.name, url.href);
1127
+ const policyUrl = `${url.origin}${url.pathname}${url.search}`;
1128
+ if (!routesByUrl.has(policyUrl))
1129
+ routesByUrl.set(policyUrl, { name: row.name, path: `${url.pathname}${url.search}` });
1130
+ }
1131
+ const routes = [...routesByUrl.values()];
1132
+ if (!routes.length)
1133
+ throw new RefusalError('browser application routes are missing or ambiguous');
1134
+ const skillBytes = browserAuthority.skillTemplate.bytes;
1135
+ if (sha256Hex(skillBytes) !== browserAuthority.skillTemplate.sha256)
1136
+ throw new RefusalError('browser skill template authority is malformed');
1137
+ const harnessRows = config.browser.harnesses.flatMap(row => isGuardSurfaceTarget(row.target)
1138
+ ? [{ target: row.target, version: row.version }] : []);
1139
+ const rendered = renderBrowserSurfaces({ runtime, cockpitDirectory: config.cockpitRoot,
1140
+ harnesses: harnessRows, mcp, approved: true,
1141
+ testPackage: observeBrowserProjectPackage({ runtime, cockpitDirectory: config.cockpitRoot,
1142
+ artifactRoot: runtime.cliArtifactRoot }), skillTemplate: skillBytes });
1143
+ const client = [...rendered.clients].sort((left, right) => left.target.localeCompare(right.target))[0];
1144
+ if (!client) {
1145
+ const measured = measuredBrowserHarnessVersions();
1146
+ const declared = harnessRows.length ? harnessRows.map(row => `${row.target} ${row.version}`).join(', ') : 'no harness cockpit can drive';
1147
+ throw new RefusalError(`authenticated browser project client is unavailable: no browser.harnesses row is at a measured version `
1148
+ + `(declared: ${declared}; measured: ${Object.entries(measured).map(([target, version]) => `${target} ${version}`).join(', ')}); `
1149
+ + `set a browser.harnesses row to a measured target and version in ${captured.configPath}, then run ${cockpitCommand('sync', { config: captured.configPath })}`);
1150
+ }
1151
+ return { runtime, client,
1152
+ policy: issueBrowserRoutePolicy(runtime, { origin: originUrl.origin, routes }),
1153
+ ...(runtime.authentication.posture === 'real' ? { state: {
1154
+ namespace: browserAuthority.namespace, stackId: loaded.result.stackId, ownerId: record.value.holderId,
1155
+ } } : {}) };
1156
+ };
1157
+ // Work orders and gates read each sibling where `cockpit use` serves it, as the stack does.
1158
+ const recorded = await readSiblingCheckouts(config.cockpitRoot);
1159
+ if (recorded.kind !== 'complete')
1160
+ return { kind: 'refusal', reason: `${recorded.diagnostic.code}: ${recorded.diagnostic.message}; ${recorded.diagnostic.fix}` };
1161
+ const served = servedSiblings(config.cockpitRoot, { cockpit: { siblings_root: config.siblingsRoot }, sibling: config.siblings }, recorded.checkouts);
1162
+ const gone = await missingServedCheckout(served);
1163
+ if (gone)
1164
+ return { kind: 'refusal', reason: `${gone.code}: ${gone.message}; ${gone.fix}` };
1165
+ const siblingRoots = config.siblings.map(row => ({ root: servedPath(served, row.slug), environmentFiles: row.environmentFiles }));
1166
+ const gateBrowserAuthenticationTargets = new Set((config.browser?.authentication.states ?? []).map(row => resolve(config.cockpitRoot, row.path)));
1167
+ const gateRepositories = [
1168
+ { identity: config.cockpitName, root: config.cockpitRoot,
1169
+ sensitivePaths: repositoryEnvironmentInstancePaths(config.cockpitRoot, siblingRoots),
1170
+ forbiddenTargets: gateBrowserAuthenticationTargets },
1171
+ ...siblingRoots.map((row, index) => ({ identity: `siblings/${config.siblings[index].slug}`, root: row.root,
1172
+ sensitivePaths: repositoryEnvironmentInstancePaths(row.root, siblingRoots),
1173
+ forbiddenTargets: gateBrowserAuthenticationTargets })),
1174
+ ];
1175
+ const reads = createReadCommandAdapters({ configPath: captured.configPath, env: captured.env,
1176
+ sessionId: 'cockpit-work-gate-read-only', timeoutMs: 30_000 }, executeProbe);
1177
+ const gateRuntime = async () => {
1178
+ try {
1179
+ const initial = await reads.environment.observe({ configPath: captured.configPath });
1180
+ return { stack: { state: 'available' }, evidence: {
1181
+ snapshot: initial.snapshot,
1182
+ capture: async () => (await reads.environment.observe({ configPath: captured.configPath })).evidence,
1183
+ } };
1184
+ }
1185
+ catch (error) {
1186
+ return { stack: { state: 'unavailable',
1187
+ reason: `authenticated runtime observation failed: ${errorCause(error)}` } };
1188
+ }
1189
+ };
1190
+ const composition = createWorkApplicationComposition(config, {
1191
+ env: captured.env, executableDirectories, declaredEnvironment,
1192
+ resolveEvaluator,
1193
+ ...(config.browser ? { resolveBrowser } : {}),
1194
+ testing: { registry: authority.testing.registry,
1195
+ dependencies: productGateDependencies(config.cockpitRoot, config.source, gateRepositories, declaredEnvironment, executableDirectories), runtime: gateRuntime },
1196
+ }, served);
1197
+ return { kind: 'complete', composition };
1198
+ }
1199
+ catch (error) {
1200
+ return { kind: 'refusal', reason: `Configured work application is unavailable: ${errorCause(error)}` };
1201
+ }
1202
+ }