@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,742 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { realpath } from 'node:fs/promises';
3
+ import { createConnection } from 'node:net';
4
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from 'node:path';
5
+ import { loadCockpitConfig, projectMachineFloorConfig } from '../config/load.js';
6
+ import { cockpitCommand } from '../fix-catalogue.js';
7
+ import { redirectedBindSources } from './compose-topology.js';
8
+ import { authenticatedExecutionOutcome } from '../environment-exec/execute.js';
9
+ import { mounted } from '../schema.js';
10
+ import { COMPOSE_OWNERSHIP_FILE, renderComposeOwnership } from './compose-ownership.js';
11
+ import { issueLifecycleRunContext, noticeLifecycleRun, } from './application/lifecycle-context.js';
12
+ import { isProcessStart, observeProcessStart, processHost, releaseDeadSessionHolders } from './claims.js';
13
+ import { errorCause, RefusalError, DOCKER_CLIENT_ENVIRONMENT, ENVIRONMENT_CODES, snapshotExecutionPlan, } from './contract.js';
14
+ import { declaresNothingToRun, runtimeNotDeclared } from './declaration.js';
15
+ import { resolveDiskCapacityExecutable, resolveDockerExecutable, resolveExecutable, resolveGitExecutable } from './executable.js';
16
+ import { readSeededAt, recordSeeded, seedDue } from './seed.js';
17
+ import { observeHostCapacity } from './host-observation.js';
18
+ import { composeProjectName, holderId, stackId } from './identity.js';
19
+ import { decodeLifecycleRecord, projectLifecycleStage, resolveLifecycleStage, } from './lifecycle-stage.js';
20
+ import { readState } from './state.js';
21
+ import { acquirePortProjection, compensateAcquiredPortHolder, } from './ports.js';
22
+ import { admitRuntimeMeasurement, measureMachineFloor, createRuntimeAdapter, observeRuntimeTopology, issueMachineFloorAuthority, OBSERVATION_WINDOW_HEADROOM_MS, observeDockerRuntime, observeRuntimeMeasurement, projectRuntimeLifecycle, runtimeMeasurementProbeObservation, } from './runtime.js';
23
+ import { readLocalRuntimeMeasurements } from './runtime-measurement-record.js';
24
+ import { consumeSetupCheckoutProjection, observeSetupCheckout, withSetupCheckoutProjection, } from './setup-observation.js';
25
+ import { selectStateRoot } from './state.js';
26
+ import { readBoundedFile } from '../bounded-read.js';
27
+ import { loadServedSiblings, missingServedCheckout, servedPath, servedSiblingVariables } from './sibling-checkouts.js';
28
+ import { recordStackInputs } from './stack-inputs.js';
29
+ const OWNER = 'lifecycle-adapter-composition';
30
+ const COMPOSE_BYTE_LIMIT = 1_048_576;
31
+ const LISTENER_CONCURRENCY = 16;
32
+ function ownerDiagnostics(code, message, target, fix) {
33
+ return [{ severity: 'error', code, message, target, ...(fix === undefined ? {} : { fix }) }];
34
+ }
35
+ function runtimeUnavailable(reason, target) {
36
+ return ownerDiagnostics(ENVIRONMENT_CODES.RUNTIME_UNAVAILABLE, reason, target);
37
+ }
38
+ /** Names each mount difference by the row that declared it, so the refusal says what to edit. */
39
+ function mountMismatchDiagnostics(mismatch, config, served) {
40
+ const bySource = new Map(config.sibling.map((sibling) => [servedPath(served, sibling.slug), sibling]));
41
+ // After `use` the running stack still mounts the checkout it started with: the sibling's served
42
+ // directory is missing and another one is mounted in its place. Compose is fine; the stack is old.
43
+ const switched = mismatch.unexpected.length === 0 ? undefined
44
+ : config.sibling.find((sibling) => mismatch.missing.includes(servedPath(served, sibling.slug)));
45
+ if (switched)
46
+ return [{
47
+ severity: 'error', code: ENVIRONMENT_CODES.MOUNT_POSTURE_UNAVAILABLE, target: `sibling:${switched.slug}`,
48
+ message: `sibling ${switched.slug} is served from ${servedPath(served, switched.slug)}, but the running stack mounts ${mismatch.unexpected.join(', ')} instead`,
49
+ fix: `if the stack started before use changed the checkout, run ${cockpitCommand('down')} and then ${cockpitCommand('up')}; `
50
+ + `otherwise bind-mount \${${switched.var}} in a Compose service and run ${cockpitCommand('check')}`,
51
+ }];
52
+ return [
53
+ ...mismatch.missing.map((source) => {
54
+ const sibling = bySource.get(source);
55
+ return {
56
+ severity: 'error', code: ENVIRONMENT_CODES.MOUNT_POSTURE_UNAVAILABLE,
57
+ target: sibling ? `sibling:${sibling.slug}` : source,
58
+ message: sibling
59
+ ? `sibling ${sibling.slug} is declared mount = "always", but no Compose service bind-mounts ${source} or a directory inside it, so cockpit will not vouch for this stack`
60
+ : `${source} is declared for the stack to mount, but no Compose service bind-mounts it, so cockpit will not vouch for this stack`,
61
+ fix: sibling
62
+ ? `bind-mount \${${sibling.var}}, or a directory inside it, in a Compose service, or set mount = "none" on sibling ${sibling.slug} in cockpit.toml; then run npx --no cockpit sync --config <your cockpit.toml> and npx --no cockpit check --config <your cockpit.toml>`
63
+ : 'bind-mount it in a Compose service or remove it from environment.generated_host_paths; then run npx --no cockpit sync --config <your cockpit.toml> and npx --no cockpit check --config <your cockpit.toml>',
64
+ };
65
+ }),
66
+ ...mismatch.unexpected.map((source) => ({
67
+ severity: 'error', code: ENVIRONMENT_CODES.MOUNT_POSTURE_UNAVAILABLE, target: source,
68
+ message: `a Compose service bind-mounts ${source}, which lies inside no mount = "always" sibling and is no environment.generated_host_paths entry, so cockpit will not hand it to a container`,
69
+ fix: 'move the bind inside a mount = "always" sibling, declare it as one or as an environment.generated_host_paths entry, or remove the bind mount; then run npx --no cockpit sync --config <your cockpit.toml> and npx --no cockpit check --config <your cockpit.toml>',
70
+ })),
71
+ ];
72
+ }
73
+ /** Names the sibling a redirected bind sits in, so the refusal says which checkout holds the link. */
74
+ function redirectedMountDiagnostics(redirected, config, served) {
75
+ const bySource = new Map(config.sibling.map((sibling) => [servedPath(served, sibling.slug), sibling]));
76
+ return redirected.map(({ bind, holder }) => {
77
+ const sibling = bySource.get(holder);
78
+ return {
79
+ severity: 'error', code: ENVIRONMENT_CODES.MOUNT_POSTURE_UNAVAILABLE, target: bind,
80
+ message: `a Compose service bind-mounts ${bind}, which resolves through a symbolic link to a path outside ${sibling ? `sibling ${sibling.slug}` : holder}, so cockpit will not hand that path to a container`,
81
+ fix: `replace the link with a real directory, or bind the path it points at directly and declare it as a mount = "always" sibling or an environment.generated_host_paths entry; then run ${cockpitCommand('check')}`,
82
+ };
83
+ });
84
+ }
85
+ // A profile no Compose service declares enables nothing, and Compose accepts it silently, so the
86
+ // stack would start without the services the person asked for.
87
+ function undefinedProfileDiagnostics(profiles, config, requested, configPath) {
88
+ return profiles.map((profile) => {
89
+ const source = config.environment.profiles.includes(profile) ? `environment.profiles in ${basename(configPath)}`
90
+ : requested.includes(profile) ? 'up --profile' : 'the profile set the running stack was started with';
91
+ return {
92
+ severity: 'error', code: 'COMPOSE_PROFILE_UNDEFINED',
93
+ target: `profile:${profile}`,
94
+ message: `profile ${profile}, from ${source}, is declared by no service in environment.compose_files, so it would start nothing`,
95
+ fix: 'add the profile to the services it should start in the Compose file, or stop requesting it: remove it from environment.profiles or --profile, and for a stack started with it run npx --no cockpit down --config <your cockpit.toml>',
96
+ };
97
+ });
98
+ }
99
+ // The runtime input naming the profile set, so `up` refuses to change the set under a running stack
100
+ // as it refuses any other change to what the stack was started from.
101
+ const ACTIVE_PROFILES_INPUT = 'active Compose profiles';
102
+ function refusal(target, message = 'lifecycle composition authority is unavailable', code = ENVIRONMENT_CODES.LIFECYCLE_EVIDENCE_INVALID) {
103
+ return {
104
+ kind: 'refusal',
105
+ status: 2,
106
+ result: null,
107
+ diagnostics: [{
108
+ severity: 'error',
109
+ code,
110
+ message,
111
+ target,
112
+ }],
113
+ };
114
+ }
115
+ function captureInput(value) {
116
+ try {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value))
118
+ return undefined;
119
+ const descriptors = Object.getOwnPropertyDescriptors(value);
120
+ const keys = ['configPath', 'env', 'sessionId', 'timeoutMs'];
121
+ const actualKeys = Reflect.ownKeys(descriptors);
122
+ const optionalKeys = ['activeProfiles', 'runtimeMeasurementProbeAuthority', 'holderProcess'].filter((key) => descriptors[key] !== undefined);
123
+ if (actualKeys.length !== keys.length + optionalKeys.length
124
+ || keys.some((key) => !descriptors[key] || !('value' in descriptors[key]))
125
+ || optionalKeys.some((key) => !('value' in descriptors[key])))
126
+ return undefined;
127
+ const configPath = descriptors.configPath.value;
128
+ const rawEnvironment = descriptors.env.value;
129
+ const sessionId = descriptors.sessionId.value;
130
+ const timeoutMs = descriptors.timeoutMs.value;
131
+ const activeProfiles = descriptors.activeProfiles?.value;
132
+ const runtimeMeasurementProbeAuthority = descriptors.runtimeMeasurementProbeAuthority?.value;
133
+ const holderProcess = descriptors.holderProcess?.value;
134
+ if (typeof configPath !== 'string' || configPath.length === 0 || configPath.includes('\0')
135
+ || typeof sessionId !== 'string' || sessionId.length === 0 || sessionId.includes('\0')
136
+ || typeof timeoutMs !== 'number' || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000
137
+ || activeProfiles !== undefined && (!Array.isArray(activeProfiles) || activeProfiles.length > 64
138
+ || new Set(activeProfiles).size !== activeProfiles.length
139
+ || activeProfiles.some((profile) => typeof profile !== 'string' || !/^[a-z0-9][a-z0-9_.-]*$/.test(profile)))
140
+ || holderProcess !== undefined && (!holderProcess || typeof holderProcess !== 'object'
141
+ || !Number.isSafeInteger(holderProcess.pid) || holderProcess.pid < 1
142
+ || holderProcess.processStart !== undefined && !isProcessStart(holderProcess.processStart))
143
+ || !rawEnvironment || typeof rawEnvironment !== 'object' || Array.isArray(rawEnvironment))
144
+ return undefined;
145
+ const env = {};
146
+ for (const [name, entry] of Object.entries(rawEnvironment)) {
147
+ if (name.length === 0 || name.includes('\0') || name.includes('=')
148
+ || entry !== undefined && (typeof entry !== 'string' || entry.includes('\0')))
149
+ return undefined;
150
+ env[name] = entry;
151
+ }
152
+ return { configPath: resolve(configPath), env, sessionId, timeoutMs,
153
+ ...(activeProfiles === undefined ? {} : { activeProfiles: [...activeProfiles].sort() }),
154
+ ...(runtimeMeasurementProbeAuthority === undefined ? {} : { runtimeMeasurementProbeAuthority }),
155
+ ...(holderProcess === undefined ? {} : { holderProcess: { pid: holderProcess.pid,
156
+ ...(holderProcess.processStart === undefined ? {} : { processStart: holderProcess.processStart }) } }) };
157
+ }
158
+ catch {
159
+ // A throwing accessor or proxy trap makes the input malformed, which the caller refuses.
160
+ return undefined;
161
+ }
162
+ }
163
+ // Literals, so the diagnostic inventory captures each fix beside its code.
164
+ function seedFailed(context, message) {
165
+ return { kind: 'refusal', diagnostics: [{
166
+ severity: 'error', code: 'DATA_SEED_FAILED',
167
+ message, target: 'environment.seed',
168
+ fix: `correct environment.seed in ${basename(context.source.path)} or the seed it runs, then rerun npx --no cockpit up --config <your cockpit.toml>`,
169
+ }] };
170
+ }
171
+ function seedUnavailable(context, message) {
172
+ return { kind: 'refusal', diagnostics: [{
173
+ severity: 'error', code: 'DATA_SEED_UNAVAILABLE',
174
+ message: `cockpit could not tell whether the seed is due: ${message}`, target: context.stackId,
175
+ fix: 'make sure the Docker daemon this stack runs on answers docker volume ls, then rerun npx --no cockpit up --config <your cockpit.toml>',
176
+ }] };
177
+ }
178
+ function runtimeEnvironment(env) {
179
+ return Object.fromEntries(Object.entries(env)
180
+ .filter((entry) => DOCKER_CLIENT_ENVIRONMENT.has(entry[0]) && typeof entry[1] === 'string'));
181
+ }
182
+ function confined(root, path) {
183
+ const relation = relative(root, path);
184
+ return relation === '' || relation !== '..' && !relation.startsWith(`..${sep}`) && !isAbsolute(relation);
185
+ }
186
+ async function composeArtifact(root, reference, expectedSha256) {
187
+ if (typeof reference !== 'string' || reference.length === 0 || reference.includes('\0'))
188
+ return undefined;
189
+ const expected = resolve(root, reference);
190
+ if (!confined(root, expected))
191
+ return undefined;
192
+ try {
193
+ const canonicalPath = await realpath(expected);
194
+ if (canonicalPath !== expected)
195
+ return undefined;
196
+ const read = readBoundedFile(canonicalPath, COMPOSE_BYTE_LIMIT);
197
+ if (read.kind !== 'complete')
198
+ return undefined;
199
+ const bytes = read.bytes;
200
+ const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
201
+ const sha256 = createHash('sha256').update(bytes).digest('hex');
202
+ if (expectedSha256 !== undefined && sha256 !== expectedSha256)
203
+ return undefined;
204
+ return { reference, canonicalPath, source, sha256 };
205
+ }
206
+ catch {
207
+ // Callers treat any unreadable or changed artifact as no file authority and refuse or report unavailable.
208
+ return undefined;
209
+ }
210
+ }
211
+ async function composeArtifacts(root, references) {
212
+ const artifacts = [];
213
+ for (const reference of references) {
214
+ const artifact = await composeArtifact(root, reference);
215
+ if (!artifact)
216
+ return undefined;
217
+ artifacts.push(artifact);
218
+ }
219
+ return artifacts;
220
+ }
221
+ async function occupiedPort(port, timeoutMs) {
222
+ return new Promise((complete) => {
223
+ const socket = createConnection({ host: '127.0.0.1', port });
224
+ let settled = false;
225
+ const finish = (value) => {
226
+ if (settled)
227
+ return;
228
+ settled = true;
229
+ socket.destroy();
230
+ complete(value);
231
+ };
232
+ socket.setTimeout(timeoutMs, () => finish(undefined));
233
+ socket.once('connect', () => finish(true));
234
+ socket.once('error', (error) => finish(error.code === 'ECONNREFUSED' ? false : undefined));
235
+ });
236
+ }
237
+ async function listenerEvidence(context, requestedAt, timeoutMs) {
238
+ const deadline = Date.parse(requestedAt) + timeoutMs;
239
+ const candidates = Array.from({ length: context.ports.count }, (_, offset) => {
240
+ const ports = Object.values(context.ports.bases).map((base) => base + offset * context.ports.step);
241
+ return { ports: ports };
242
+ });
243
+ const observed = candidates.map(({ ports }) => ports.map(() => undefined));
244
+ const tasks = candidates.flatMap(({ ports }, family) => ports.map((port, index) => ({ family, index, port })));
245
+ let cursor = 0;
246
+ await Promise.all(Array.from({ length: Math.min(LISTENER_CONCURRENCY, tasks.length) }, async () => {
247
+ while (cursor < tasks.length) {
248
+ const task = tasks[cursor++];
249
+ const remaining = deadline - Date.now();
250
+ if (remaining <= 0)
251
+ return;
252
+ observed[task.family][task.index] = Number.isSafeInteger(task.port) && task.port >= 1 && task.port <= 65_535
253
+ ? await occupiedPort(task.port, remaining)
254
+ : undefined;
255
+ }
256
+ }));
257
+ const completedAt = Date.now();
258
+ return candidates.map(({ ports }, family) => {
259
+ const familyObservation = observed[family];
260
+ const unavailable = completedAt > deadline || familyObservation.length !== ports.length
261
+ || familyObservation.some((value) => value === undefined);
262
+ return {
263
+ ports,
264
+ observation: unavailable
265
+ ? {
266
+ state: 'unavailable',
267
+ reason: 'bounded loopback listener observation is unavailable',
268
+ producer: OWNER,
269
+ }
270
+ : {
271
+ state: 'available',
272
+ value: ports.filter((_port, index) => familyObservation[index] === true),
273
+ observedAt: new Date(Math.max(completedAt, Date.parse(requestedAt))).toISOString(),
274
+ producer: OWNER,
275
+ },
276
+ };
277
+ });
278
+ }
279
+ export function createLifecycleCommandAdapters(rawInput, execute) {
280
+ const input = captureInput(rawInput);
281
+ const bindings = new WeakMap();
282
+ const issuedRuntimeReadiness = new WeakSet();
283
+ const load = async (command) => {
284
+ if (!input || typeof execute !== 'function' || command.configPath !== input.configPath) {
285
+ return refusal(typeof command?.configPath === 'string' ? command.configPath : 'lifecycle');
286
+ }
287
+ const loaded = await loadCockpitConfig(input.configPath);
288
+ if (loaded.kind === 'refusal')
289
+ return loaded;
290
+ if (declaresNothingToRun(loaded.result.config.environment))
291
+ return { kind: 'refusal',
292
+ status: 2, result: null, diagnostics: [runtimeNotDeclared(loaded.result.source.path)] };
293
+ const served = await loadServedSiblings(dirname(loaded.result.source.path), loaded.result.config);
294
+ if (served.kind === 'refusal')
295
+ return { kind: 'refusal', status: 2, result: null, diagnostics: [served.diagnostic] };
296
+ const setupEnvironment = Object.fromEntries(Object.entries(input.env)
297
+ .filter((entry) => typeof entry[1] === 'string'));
298
+ const git = await resolveGitExecutable(input.env);
299
+ if (git.state !== 'available')
300
+ return refusal(loaded.result.source.path, git.reason);
301
+ const observedAt = new Date().toISOString();
302
+ const checkoutObservation = await observeSetupCheckout({
303
+ source: loaded.result.source,
304
+ cockpitName: loaded.result.config.cockpit.name,
305
+ observedAt,
306
+ env: setupEnvironment,
307
+ gitExecutable: git.value,
308
+ });
309
+ const machineConfiguration = projectMachineFloorConfig(loaded.result);
310
+ const projected = await withSetupCheckoutProjection(checkoutObservation, {
311
+ source: loaded.result.source,
312
+ cockpitName: loaded.result.config.cockpit.name,
313
+ evaluatedAt: Date.parse(observedAt),
314
+ }, (projection) => machineConfiguration
315
+ ? issueMachineFloorAuthority({ configuration: machineConfiguration, checkout: projection })
316
+ : consumeSetupCheckoutProjection(projection));
317
+ const checkout = projected && 'authority' in projected ? projected.checkout : projected;
318
+ if (!checkout || process.platform !== 'darwin' && process.platform !== 'linux') {
319
+ return refusal(loaded.result.source.path, checkoutObservation.state === 'failed'
320
+ ? checkoutObservation.reason : 'authenticated checkout evidence is unavailable');
321
+ }
322
+ const namespace = await selectStateRoot({
323
+ platform: process.platform,
324
+ home: input.env.HOME,
325
+ xdgStateHome: input.env.XDG_STATE_HOME,
326
+ checkoutRoots: [checkout.currentCheckoutPath, checkout.mainCheckoutPath],
327
+ cockpitId: checkout.cockpitId,
328
+ });
329
+ // The state root's own refusal names the directory and the failed check; replacing it with a
330
+ // generic one left a sandboxed agent no way to tell why every lifecycle command refused.
331
+ if (namespace.kind === 'refusal')
332
+ return refusal(namespace.target, namespace.message, namespace.code);
333
+ if (namespace.kind !== 'complete')
334
+ return refusal(loaded.result.source.path, 'state namespace is unavailable');
335
+ const currentStackId = stackId({
336
+ cockpitId: checkout.cockpitId,
337
+ checkoutId: checkout.checkoutId,
338
+ variant: { kind: 'shared' },
339
+ });
340
+ const currentHolderId = holderId({
341
+ stackId: currentStackId,
342
+ checkoutId: checkout.checkoutId,
343
+ sessionId: input.sessionId,
344
+ });
345
+ const confirmedAt = new Date().toISOString();
346
+ const confirmed = await observeSetupCheckout({
347
+ source: loaded.result.source,
348
+ cockpitName: loaded.result.config.cockpit.name,
349
+ observedAt: confirmedAt,
350
+ env: setupEnvironment,
351
+ gitExecutable: git.value,
352
+ });
353
+ const issued = await withSetupCheckoutProjection(confirmed, {
354
+ source: loaded.result.source,
355
+ cockpitName: loaded.result.config.cockpit.name,
356
+ evaluatedAt: Date.parse(confirmedAt),
357
+ }, (projection) => issueLifecycleRunContext({
358
+ configPath: input.configPath,
359
+ checkout: projection,
360
+ configuration: loaded.result,
361
+ namespace: namespace.value,
362
+ stackId: currentStackId,
363
+ holderId: currentHolderId,
364
+ sessionId: input.sessionId,
365
+ }));
366
+ if (!issued || issued.kind !== 'complete')
367
+ return issued ?? refusal(loaded.result.source.path);
368
+ bindings.set(issued.result, {
369
+ config: loaded.result.config,
370
+ checkoutRoot: dirname(loaded.result.source.path),
371
+ siblingsRoot: resolve(dirname(loaded.result.source.path), loaded.result.config.cockpit.siblings_root),
372
+ served: served.siblings,
373
+ ...(projected && 'authority' in projected ? { machineFloorAuthority: projected.authority } : {}),
374
+ });
375
+ return { ...issued, diagnostics: loaded.diagnostics };
376
+ };
377
+ const hostStep = (id, executable, args, cwd, env, timeoutMs, stackId) => snapshotExecutionPlan({ steps: [{
378
+ id, argv: [executable.canonicalPath, ...args],
379
+ argv0: executable.invocationPath, cwd, env, timeoutMs,
380
+ stdoutLimitBytes: 1_048_576, stderrLimitBytes: 65_536, outputOverflow: 'retain-tail',
381
+ paths: [], ports: [], labels: { stackId }, expected: [],
382
+ consumedFiles: [{ path: executable.canonicalPath, sha256: executable.sha256, maxBytes: 536_870_912,
383
+ argvIndexes: [0], executable: true, executionMode: executable.executionMode }],
384
+ filesystemChecks: [], compensation: [],
385
+ }] })?.steps[0];
386
+ // When each resettable volume of this stack was created, by its Compose key; a volume no service
387
+ // has created yet has no entry. Undefined when Docker could not say.
388
+ const resettableVolumesCreatedAt = async (context, binding) => {
389
+ if (!input)
390
+ return undefined;
391
+ const docker = await resolveDockerExecutable(input.env);
392
+ if (docker.state !== 'available')
393
+ return undefined;
394
+ const env = runtimeEnvironment(input.env);
395
+ const project = composeProjectName({ cockpitName: binding.config.cockpit.name, stackId: context.stackId });
396
+ const listing = hostStep('seed:volume-list', docker.value, ['volume', 'ls', '--filter', `label=com.docker.compose.project=${project}`,
397
+ '--format', '{{.Name}}\t{{.Label "com.docker.compose.volume"}}'], binding.checkoutRoot, env, input.timeoutMs, context.stackId);
398
+ const listed = listing && authenticatedExecutionOutcome(await execute(listing), listing);
399
+ if (!listed || listed.kind !== 'complete' || listed.execution.exitCode !== 0)
400
+ return undefined;
401
+ const keys = new Set(binding.config.environment.resettable_volumes);
402
+ const names = Buffer.from(listed.stdout).toString('utf8').split('\n').filter(Boolean)
403
+ .map((line) => line.split('\t')).filter(([, key]) => key !== undefined && keys.has(key)).map(([name]) => name);
404
+ if (names.length === 0)
405
+ return [];
406
+ const inspection = hostStep('seed:volume-created', docker.value, ['volume', 'inspect', '--format', '{{json .CreatedAt}}', ...names], binding.checkoutRoot, env, input.timeoutMs, context.stackId);
407
+ const inspected = inspection && authenticatedExecutionOutcome(await execute(inspection), inspection);
408
+ if (!inspected || inspected.kind !== 'complete' || inspected.execution.exitCode !== 0)
409
+ return undefined;
410
+ // `{{json .CreatedAt}}` prints one quoted instant per volume; anything else is unreadable.
411
+ const created = Buffer.from(inspected.stdout).toString('utf8').trim().split('\n')
412
+ .map((line) => /^"[^"\\]+"$/.test(line) ? Date.parse(line.slice(1, -1)) : Number.NaN);
413
+ return created.length === names.length && created.every(Number.isFinite) ? created : undefined;
414
+ };
415
+ const seed = async (context, up) => {
416
+ const binding = bindings.get(context);
417
+ const declared = binding?.config.environment.seed;
418
+ if (!input || !binding)
419
+ return seedUnavailable(context, 'the lifecycle binding is unavailable');
420
+ if (!declared)
421
+ return { kind: 'none' };
422
+ const [seededAt, created] = await Promise.all([readSeededAt(context.namespace, context.stackId),
423
+ resettableVolumesCreatedAt(context, binding)]);
424
+ if (seededAt === undefined)
425
+ return seedUnavailable(context, 'the record of the last seed is unreadable');
426
+ if (created === undefined)
427
+ return seedUnavailable(context, 'Docker did not report when the resettable volumes were created');
428
+ if (!seedDue(seededAt, created))
429
+ return { kind: 'current' };
430
+ const env = {
431
+ ...Object.fromEntries(Object.entries(input.env).filter((entry) => typeof entry[1] === 'string')),
432
+ ...Object.fromEntries(Object.entries(up.ports).map(([name, port]) => [name, String(port)])),
433
+ ...Object.fromEntries(up.entrypoints.map(({ name, value }) => [`COCKPIT_ENTRYPOINT_${name.toUpperCase().replaceAll('-', '_')}`, value])),
434
+ };
435
+ const executable = await resolveExecutable(declared.argv[0], env);
436
+ if (executable.state !== 'available')
437
+ return seedFailed(context, `environment.seed names ${declared.argv[0]}, which could not be run: ${executable.reason}`);
438
+ const step = hostStep('seed:run', executable.value, declared.argv.slice(1), binding.checkoutRoot, env, declared.timeout_seconds * 1_000, context.stackId);
439
+ if (!step)
440
+ return seedFailed(context, 'the seed command could not be planned within the execution bounds');
441
+ const ran = authenticatedExecutionOutcome(await execute(step), step);
442
+ if (!ran || ran.kind !== 'complete' || ran.execution.exitCode !== 0) {
443
+ const status = !ran ? 'did not run' : ran.execution.signal ? `was stopped by ${ran.execution.signal}`
444
+ : ran.kind === 'refusal' && ran.code !== ENVIRONMENT_CODES.EXECUTION_STEP_FAILED ? `did not complete (${ran.code})`
445
+ : `exited with status ${ran.execution.exitCode}`;
446
+ return seedFailed(context, `the seed command ${status}; its data may be partly applied, and it runs again on the next up`);
447
+ }
448
+ const recorded = await recordSeeded({ namespace: context.namespace, stackId: context.stackId,
449
+ holderId: context.holderId, seededAt: new Date().toISOString() });
450
+ return recorded ? { kind: 'ran' }
451
+ : seedUnavailable(context, 'the seed ran, and its success could not be recorded, so the next up runs it again');
452
+ };
453
+ const withRuntimeReadiness = (context, use, ports, refuse, purpose) => runtimeReadiness(context, use, ports, refuse, purpose);
454
+ // `resolvedMounts` is set only on a teardown retry: stopping or removing a stack must not depend
455
+ // on its mounts matching cockpit.toml, or a stack whose declaration drifted could never be retired.
456
+ const runtimeReadiness = async (context, use, ports, refuse, purpose, resolvedMounts,
457
+ // Set only on a teardown retry, for the same reason: a recorded profile the Compose files no
458
+ // longer declare must not keep down and sleep from retiring the stack.
459
+ droppedProfiles = []) => {
460
+ const binding = bindings.get(context);
461
+ if (!input || !binding)
462
+ return undefined;
463
+ const currentBinding = binding;
464
+ // The machine floor bounds cold start and host capacity; without it there is nothing to hold
465
+ // the runtime to, so say which table is missing instead of a generic evidence refusal.
466
+ const machine = currentBinding.config.environment.machine;
467
+ if (!machine) {
468
+ return refuse?.(ownerDiagnostics(ENVIRONMENT_CODES.CONFIG_SCHEMA_INVALID, 'cockpit.toml declares no [environment.machine] table; runtime commands need its runtime_memory_mb, runtime_cpus, free_disk_mb and cold_start_seconds', 'environment.machine'));
469
+ }
470
+ // Teardown must reach a stack whatever it mounts; anything else would mount a directory that is gone.
471
+ if (purpose !== 'teardown') {
472
+ const gone = await missingServedCheckout(currentBinding.served);
473
+ if (gone)
474
+ return refuse?.([gone]);
475
+ }
476
+ const env = runtimeEnvironment(input.env);
477
+ const docker = await resolveDockerExecutable(input.env);
478
+ if (docker.state !== 'available')
479
+ return refuse?.(runtimeUnavailable(docker.reason, context.stackId));
480
+ let probeAuthority;
481
+ if (input.runtimeMeasurementProbeAuthority !== undefined) {
482
+ try {
483
+ probeAuthority = typeof input.runtimeMeasurementProbeAuthority === 'function'
484
+ ? input.runtimeMeasurementProbeAuthority() : input.runtimeMeasurementProbeAuthority;
485
+ }
486
+ catch (error) {
487
+ throw new RefusalError('runtime measurement probe authority supplier failed', { cause: error });
488
+ }
489
+ }
490
+ const observed = input.runtimeMeasurementProbeAuthority === undefined
491
+ ? await observeDockerRuntime({
492
+ executable: docker.value,
493
+ cwd: binding.checkoutRoot,
494
+ env,
495
+ timeoutMs: input.timeoutMs,
496
+ }, execute)
497
+ : (probeAuthority === undefined ? undefined : runtimeMeasurementProbeObservation(probeAuthority, docker.value.sha256))
498
+ ?? { kind: 'unavailable', code: ENVIRONMENT_CODES.RUNTIME_UNAVAILABLE, reason: 'runtime measurement probe authority is unavailable or expired' };
499
+ if (observed.kind !== 'complete')
500
+ return refuse?.(ownerDiagnostics(observed.code, observed.reason, context.stackId, 'fix' in observed ? observed.fix : undefined));
501
+ const measured = admitRuntimeMeasurement(observed, await readLocalRuntimeMeasurements(context.namespace.root));
502
+ const certifiedProbe = input.runtimeMeasurementProbeAuthority !== undefined;
503
+ if (measured.kind !== 'admitted' && !certifiedProbe)
504
+ return refuse?.(ownerDiagnostics(measured.code, measured.reason, context.stackId));
505
+ const measurementObservation = measured.kind === 'admitted'
506
+ ? observeRuntimeMeasurement(measured, context.stackId) : undefined;
507
+ let artifacts;
508
+ let inputs = { [basename(context.source.path)]: context.runtimeSha256 };
509
+ // A running stack keeps the profiles it started with, so every later command, down
510
+ // included, sees the services they enable; the run adds the declared default set and any
511
+ // profiles `up --profile` requested.
512
+ const recorded = await readState(context.namespace.paths.lifecycle(context.stackId));
513
+ if (recorded.kind !== 'complete' && recorded.kind !== 'missing')
514
+ return refuse?.(ownerDiagnostics(recorded.code, recorded.message, recorded.target));
515
+ const recordedValue = recorded.kind === 'complete' ? decodeLifecycleRecord(recorded.value, context)?.value : undefined;
516
+ if (recorded.kind === 'complete' && !recordedValue)
517
+ return refuse?.(ownerDiagnostics(ENVIRONMENT_CODES.STATE_RECORD_MALFORMED, 'lifecycle progress record is malformed', context.namespace.paths.lifecycle(context.stackId).path));
518
+ const recordedProfiles = recordedValue?.activeProfiles ?? [];
519
+ const activeProfiles = [...new Set([...currentBinding.config.environment.profiles,
520
+ ...input.activeProfiles ?? [], ...recordedProfiles])].filter((profile) => !droppedProfiles.includes(profile)).sort();
521
+ let expectedMountSources;
522
+ const declared = await composeArtifacts(currentBinding.checkoutRoot, currentBinding.config.environment.compose_files);
523
+ if (!declared)
524
+ return undefined;
525
+ // The cockpit's own Compose files carry no ownership labels; the override `sync`
526
+ // publishes adds them, and it is admitted only while it equals a fresh render.
527
+ const ownership = renderComposeOwnership({
528
+ sources: declared.map(({ reference, source }) => ({ reference, source })),
529
+ stackId: context.stackId, cockpitId: context.cockpitId, configSha256: context.runtimeSha256,
530
+ resettableVolumes: currentBinding.config.environment.resettable_volumes,
531
+ });
532
+ if (ownership.kind !== 'complete')
533
+ return refuse?.(ownerDiagnostics(ENVIRONMENT_CODES.GENERATED_ENVIRONMENT_UNAVAILABLE, `the ownership override cannot be rendered from environment.compose_files: ${ownership.reason}`, ownership.target));
534
+ const override = await composeArtifact(currentBinding.checkoutRoot, COMPOSE_OWNERSHIP_FILE, createHash('sha256').update(ownership.bytes).digest('hex'));
535
+ if (!override)
536
+ return refuse?.(ownerDiagnostics(ENVIRONMENT_CODES.GENERATED_ENVIRONMENT_UNAVAILABLE, `${COMPOSE_OWNERSHIP_FILE} is missing or does not match the current Compose files and cockpit.toml; run ${cockpitCommand('sync')}`, COMPOSE_OWNERSHIP_FILE));
537
+ artifacts = [...declared, override];
538
+ inputs = { ...inputs, ...Object.fromEntries(declared.map(({ reference, sha256 }) => [reference, sha256])),
539
+ ...(activeProfiles.length === 0 ? {} : {
540
+ [ACTIVE_PROFILES_INPUT]: createHash('sha256').update(activeProfiles.join('\n')).digest('hex')
541
+ }) };
542
+ expectedMountSources = [...new Set([
543
+ ...mounted(currentBinding.config, activeProfiles)
544
+ .map((sibling) => servedPath(currentBinding.served, sibling.slug)),
545
+ ...currentBinding.config.environment.generated_host_paths
546
+ .map((path) => resolve(currentBinding.checkoutRoot, path)),
547
+ ])].sort();
548
+ if (!artifacts || artifacts.length === 0)
549
+ return undefined;
550
+ if (resolvedMounts)
551
+ expectedMountSources = resolvedMounts;
552
+ const siblingDirectories = servedSiblingVariables(currentBinding.config, currentBinding.served);
553
+ const batchId = randomBytes(32).toString('hex');
554
+ const runtime = createRuntimeAdapter({
555
+ runtime: observed.runtime,
556
+ composeVersion: observed.composeVersion,
557
+ executable: docker.value,
558
+ operation: 'validate',
559
+ cwd: currentBinding.checkoutRoot,
560
+ composeFiles: artifacts,
561
+ projectName: composeProjectName({ cockpitName: currentBinding.config.cockpit.name, stackId: context.stackId }),
562
+ env,
563
+ ports: ports ? Object.values(ports.lease.ports) : [],
564
+ labels: { stackId: context.stackId, cockpitId: context.cockpitId, configSha256: context.runtimeSha256, batchId },
565
+ timeoutMs: input.timeoutMs,
566
+ stackId: context.stackId,
567
+ coldStartLimitMillis: machine.cold_start_seconds * 1_000,
568
+ expectedMountSources,
569
+ activeProfiles,
570
+ // The stack reads its host ports from the [ports.bases] variables and its sibling checkouts
571
+ // from the [[sibling]] var variables, so both reach Compose as those variables instead of the
572
+ // `${VAR:-default}` fallback.
573
+ }, { ownComposeFiles: true, cockpitId: context.cockpitId, stackId: context.stackId,
574
+ ...(ports ? { portLeaseProjection: ports } : {}), siblingDirectories });
575
+ if (runtime.kind !== 'preview')
576
+ return runtime.kind === 'refusal' ? refuse?.(runtime.diagnostics) : undefined;
577
+ const startedAt = new Date().toISOString();
578
+ const topology = await observeRuntimeTopology({
579
+ runtimePlan: runtime.plan,
580
+ stackId: context.stackId,
581
+ configSha256: context.runtimeSha256,
582
+ batchId,
583
+ observationWindow: {
584
+ startedAt,
585
+ expiresAt: new Date(Date.parse(startedAt) + machine.cold_start_seconds * 1_000 + OBSERVATION_WINDOW_HEADROOM_MS).toISOString(),
586
+ },
587
+ execute,
588
+ });
589
+ if (topology.kind !== 'complete') {
590
+ if (topology.undefinedProfiles && purpose === 'teardown') {
591
+ return runtimeReadiness(context, use, ports, refuse, purpose, resolvedMounts, [...droppedProfiles, ...topology.undefinedProfiles]);
592
+ }
593
+ if (topology.undefinedProfiles)
594
+ return refuse?.(undefinedProfileDiagnostics(topology.undefinedProfiles, currentBinding.config, input.activeProfiles ?? [], context.source.path));
595
+ const mismatch = topology.mounts;
596
+ if (!mismatch)
597
+ return undefined;
598
+ if (purpose === 'teardown' && !resolvedMounts) {
599
+ const resolved = [...expectedMountSources.filter((source) => !mismatch.missing.includes(source)), ...mismatch.unexpected].sort();
600
+ return runtimeReadiness(context, use, ports, refuse, purpose, resolved, droppedProfiles);
601
+ }
602
+ return refuse?.(mountMismatchDiagnostics(mismatch, currentBinding.config, currentBinding.served));
603
+ }
604
+ if (purpose !== 'teardown') {
605
+ const redirected = await redirectedBindSources(topology.topology.services.flatMap(({ bindMountSources }) => bindMountSources), expectedMountSources);
606
+ if (redirected.length > 0)
607
+ return refuse?.(redirectedMountDiagnostics(redirected, currentBinding.config, currentBinding.served));
608
+ }
609
+ const disk = await resolveDiskCapacityExecutable();
610
+ if (disk.state !== 'available')
611
+ return undefined;
612
+ const floor = await measureMachineFloor({
613
+ runtimePlan: runtime.plan,
614
+ diskExecutable: disk.value,
615
+ timeoutMs: input.timeoutMs,
616
+ authority: currentBinding.machineFloorAuthority,
617
+ }, execute);
618
+ if (floor.kind !== 'complete')
619
+ return undefined;
620
+ const hostObservations = observeHostCapacity({ evidence: floor.evidence });
621
+ const lifecycle = projectRuntimeLifecycle(topology.topology);
622
+ if (!lifecycle)
623
+ return undefined;
624
+ const readiness = {
625
+ topology: topology.topology,
626
+ lifecycle,
627
+ hostObservations,
628
+ runtimeObservations: measurementObservation
629
+ ? [measurementObservation, ...topology.observations] : [...topology.observations],
630
+ inputs,
631
+ activeProfiles,
632
+ };
633
+ issuedRuntimeReadiness.add(readiness);
634
+ return use(readiness);
635
+ };
636
+ const withMachineFloorReadiness = async (context, use, refuse) => {
637
+ const binding = bindings.get(context);
638
+ if (!input || !binding?.machineFloorAuthority || !binding.config.environment.machine)
639
+ return undefined;
640
+ const env = runtimeEnvironment(input.env);
641
+ const [docker, disk] = await Promise.all([
642
+ resolveDockerExecutable(input.env),
643
+ resolveDiskCapacityExecutable(),
644
+ ]);
645
+ if (docker.state !== 'available')
646
+ return refuse?.(runtimeUnavailable(docker.reason, context.stackId));
647
+ if (disk.state !== 'available')
648
+ return refuse?.(runtimeUnavailable(disk.reason, context.stackId));
649
+ const machine = await measureMachineFloor({
650
+ runtimeExecutable: docker.value,
651
+ diskExecutable: disk.value,
652
+ cwd: binding.checkoutRoot,
653
+ env,
654
+ timeoutMs: input.timeoutMs,
655
+ authority: binding.machineFloorAuthority,
656
+ }, execute);
657
+ if (machine.kind === 'refusal')
658
+ return refuse?.(machine.diagnostics);
659
+ return machine.kind === 'complete' ? use(observeHostCapacity({ evidence: machine.evidence })) : undefined;
660
+ };
661
+ const withLifecycleStage = async (context, use, refuse) => {
662
+ if (!bindings.has(context))
663
+ return undefined;
664
+ const observedAt = new Date().toISOString();
665
+ const resolved = await resolveLifecycleStage({ namespace: context.namespace, stackId: context.stackId, observedAt });
666
+ if (resolved.kind !== 'complete')
667
+ return refuse?.(resolved.diagnostics);
668
+ const projection = projectLifecycleStage({
669
+ authority: resolved.authority,
670
+ stackId: context.stackId,
671
+ cockpitId: context.cockpitId,
672
+ earliest: Date.parse(resolved.authority.observedAt),
673
+ latest: Date.parse(resolved.authority.validThrough),
674
+ evaluatedAt: Date.now(),
675
+ });
676
+ return projection ? use(projection) : undefined;
677
+ };
678
+ const withAcquiredPorts = async (context, use, refuse) => {
679
+ if (!bindings.has(context))
680
+ return undefined;
681
+ const holderProcess = input?.holderProcess;
682
+ const reaped = await releaseDeadSessionHolders({
683
+ namespace: context.namespace, stackId: context.stackId,
684
+ successor: { holderId: context.holderId, checkoutId: context.checkoutId, ...holderProcess },
685
+ now: new Date().toISOString(), observe: (pid) => observeProcessStart(pid, processHost(execute)),
686
+ });
687
+ if (reaped.kind === 'refusal')
688
+ return refuse?.(reaped.diagnostics);
689
+ const now = new Date().toISOString();
690
+ const listenerTimeoutMs = Math.min(input?.timeoutMs ?? 1, 30_000);
691
+ const acquired = await acquirePortProjection({
692
+ namespace: context.namespace,
693
+ stackId: context.stackId,
694
+ holderId: context.holderId,
695
+ checkoutId: context.checkoutId,
696
+ config: context.ports,
697
+ now,
698
+ listenerTimeoutMs,
699
+ listenerEvidence: await listenerEvidence(context, now, listenerTimeoutMs),
700
+ declarations: context.entrypointDerivatives,
701
+ ...(holderProcess === undefined ? {} : { holderProcess }),
702
+ });
703
+ if (acquired.kind === 'refusal')
704
+ return refuse?.(acquired.diagnostics);
705
+ if (acquired.kind !== 'complete')
706
+ return undefined;
707
+ let retainOnFailure = false;
708
+ const compensationFailure = (error, cleanup) => {
709
+ return new RefusalError(`downstream port preparation failed: ${errorCause(error)}; acquired-holder compensation failed: ${errorCause(cleanup)}`, { cause: new AggregateError([error, cleanup]) });
710
+ };
711
+ try {
712
+ return await use({
713
+ leaseResult: acquired.leaseResult,
714
+ projection: acquired.projection,
715
+ retainOnFailure: () => { retainOnFailure = true; },
716
+ });
717
+ }
718
+ catch (error) {
719
+ if (!retainOnFailure && acquired.leaseResult.kind === 'complete' && acquired.leaseResult.change === 'acquired') {
720
+ let compensated;
721
+ try {
722
+ compensated = await compensateAcquiredPortHolder({ namespace: context.namespace,
723
+ leaseResult: acquired.leaseResult, now: new Date().toISOString() });
724
+ }
725
+ catch (cleanup) {
726
+ throw compensationFailure(error, cleanup);
727
+ }
728
+ if (compensated.kind !== 'complete') {
729
+ const cleanup = new RefusalError(compensated.diagnostics
730
+ .map(({ code, message, target }) => `${code}: ${message}${target ? ` (${target})` : ''}`).join('; '));
731
+ throw compensationFailure(error, cleanup);
732
+ }
733
+ }
734
+ throw error;
735
+ }
736
+ };
737
+ const recordInputs = async (context, change) => {
738
+ const binding = bindings.get(context);
739
+ return binding ? recordStackInputs(binding.checkoutRoot, binding.config, binding.served, { keepExisting: change !== 'started' }) : [];
740
+ };
741
+ return { load, execute, seed, recordStackInputs: recordInputs, withRuntimeReadiness, withMachineFloorReadiness, withLifecycleStage, withAcquiredPorts };
742
+ }