@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,210 @@
1
+ /**
2
+ * Owns the stale-stack detector: what each mounted sibling's dependencies and schema looked like
3
+ * when the stack came up, and whether they still do. A bind mount follows a pulled branch at once,
4
+ * but installed dependencies usually live in an image or a volume built before it and a database
5
+ * keeps the schema it was migrated to, so a stack that kept running after a lockfile or migration
6
+ * changed fails like a product bug and its tests pass against code nobody is running.
7
+ *
8
+ * It decides nothing: `up` records, `down` and a stopping `sleep` forget, and doctor and preflight
9
+ * warn. The record is machine-local beside the sibling checkout record, under `.cockpit/local`.
10
+ */
11
+ import { lstatIfPresent } from './presence.js';
12
+ import { createHash } from 'node:crypto';
13
+ import { lstat, readdir, readFile, readlink, rm } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ import { readBoundedFile } from '../bounded-read.js';
16
+ import { cockpitCommand } from '../fix-catalogue.js';
17
+ import { sha256Hex } from '../sha256.js';
18
+ import { GUARD_LOCAL_PATHS, writeCockpitLocalFiles } from '../guards/local-identity.js';
19
+ import { ENVIRONMENT_CODES, errorCause, hasErrorCode, RefusalError, tryParseJson } from './contract.js';
20
+ import { mapLimit } from './map-limit.js';
21
+ import { servedPath } from './sibling-checkouts.js';
22
+ const FORMAT = 'cockpit-stack-inputs/v1';
23
+ const RECORD_NAME = 'stack-inputs';
24
+ const RECORD_PATH = `${GUARD_LOCAL_PATHS.directory}/${RECORD_NAME}`;
25
+ const RECORD_MAX_BYTES = 1_048_576;
26
+ // A migrations directory is small; one this large is the wrong path, and hashing it would stall every doctor.
27
+ const DIRECTORY_ENTRY_LIMIT = 20_000;
28
+ // Each in-flight digest may hold a file descriptor.
29
+ const DIGEST_CONCURRENCY = 16;
30
+ // The lockfiles the common toolchains write, watched when no install policy names the sibling's.
31
+ const COMMON_LOCKFILES = Object.freeze([
32
+ 'package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb',
33
+ 'poetry.lock', 'uv.lock', 'Pipfile.lock', 'pdm.lock', 'Gemfile.lock', 'go.sum', 'Cargo.lock',
34
+ 'composer.lock', 'packages.lock.json', 'mix.lock',
35
+ ]);
36
+ // Directory hashes name each entry relative to the directory, so moving a checkout is no change.
37
+ async function digestPath(path) {
38
+ let status;
39
+ try {
40
+ status = await lstat(path);
41
+ }
42
+ catch (error) {
43
+ if (hasErrorCode(error, 'ENOENT', 'ENOTDIR'))
44
+ return null;
45
+ throw error;
46
+ }
47
+ if (status.isSymbolicLink())
48
+ return sha256Hex(`link\0${await readlink(path)}`);
49
+ if (status.isFile())
50
+ return sha256Hex(await readFile(path));
51
+ if (!status.isDirectory())
52
+ return sha256Hex(`other\0${status.mode}`);
53
+ const hash = createHash('sha256');
54
+ // Entries are digested concurrently but folded into the hash in sorted order, so the digest is deterministic.
55
+ let entries = 0;
56
+ const walk = async (directory, prefix) => {
57
+ const listed = (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name < right.name ? -1 : 1);
58
+ entries += listed.length;
59
+ if (entries > DIRECTORY_ENTRY_LIMIT)
60
+ throw new RefusalError(`${path} holds more than ${DIRECTORY_ENTRY_LIMIT} entries`);
61
+ return (await mapLimit(listed, DIGEST_CONCURRENCY, async (entry) => {
62
+ const relative = `${prefix}${entry.name}`;
63
+ return entry.isDirectory()
64
+ ? [`dir\0${relative}\0`, ...await walk(join(directory, entry.name), `${relative}/`)]
65
+ : [`${relative}\0${await digestPath(join(directory, entry.name))}\0`];
66
+ })).flat();
67
+ };
68
+ for (const line of await walk(path, ''))
69
+ hash.update(line);
70
+ return hash.digest('hex');
71
+ }
72
+ async function exists(path) {
73
+ return await lstatIfPresent(path) !== undefined;
74
+ }
75
+ /** The paths watched for one sibling and where each came from: its own list, then the toolchain's lockfiles. */
76
+ async function watchedInputs(config, sibling, served) {
77
+ const declared = (sibling.rebuild_inputs ?? []).map(path => ({ path, source: 'rebuild_inputs' }));
78
+ const policies = config.environment.install_policies.filter(policy => policy.toolchain === sibling.toolchain)
79
+ .map(policy => ({ path: policy.lockfile, source: 'environment.install_policies' }));
80
+ // Without a policy only the lockfiles present at up are watched; one created later is a new
81
+ // toolchain, which a person adds to install_policies.
82
+ const common = policies.length > 0 ? [] : (await Promise.all(COMMON_LOCKFILES.map(async (path) => await exists(join(served, path)) ? [{ path, source: 'common lockfile names' }] : []))).flat();
83
+ const seen = new Set();
84
+ return [...declared, ...policies, ...common].filter(row => !seen.has(row.path) && seen.add(row.path));
85
+ }
86
+ function stackSiblings(config) {
87
+ return config.sibling.filter(sibling => sibling.mount === 'always' || sibling.mount.startsWith('profile:'));
88
+ }
89
+ /**
90
+ * Record the inputs of every sibling the stack mounts; a warning, never a refusal, when that fails.
91
+ * With `keepExisting`, a record already there stays: the stack it describes is still the one running.
92
+ */
93
+ export async function recordStackInputs(cockpitRoot, config, served, options) {
94
+ try {
95
+ if (options.keepExisting) {
96
+ try {
97
+ await lstat(join(cockpitRoot, RECORD_PATH));
98
+ return [];
99
+ }
100
+ catch (error) {
101
+ if (!hasErrorCode(error, 'ENOENT'))
102
+ throw error;
103
+ }
104
+ }
105
+ const siblings = await Promise.all(stackSiblings(config).map(async (sibling) => {
106
+ const directory = servedPath(served, sibling.slug);
107
+ const inputs = await Promise.all((await watchedInputs(config, sibling, directory)).map(async (row) => ({ ...row, sha256: await digestPath(join(directory, row.path)) })));
108
+ return { slug: sibling.slug, served: directory, inputs };
109
+ }));
110
+ const record = { format: FORMAT, recordedAt: new Date().toISOString(), siblings };
111
+ const written = await writeCockpitLocalFiles(cockpitRoot, [{ name: RECORD_NAME, bytes: `${JSON.stringify(record)}\n` }]);
112
+ if (written.kind !== 'complete')
113
+ throw new RefusalError(written.message);
114
+ return [];
115
+ }
116
+ catch (error) {
117
+ return [unrecorded(`the lockfiles and declared rebuild inputs could not be recorded (${errorCause(error)})`)];
118
+ }
119
+ }
120
+ function unrecorded(reason) {
121
+ return {
122
+ severity: 'warning',
123
+ code: 'STACK_INPUTS_UNRECORDED',
124
+ message: `${reason}, so doctor and preflight cannot tell when this stack falls behind its siblings' dependencies or migrations`,
125
+ target: RECORD_PATH,
126
+ fix: `run ${cockpitCommand('down')} and ${cockpitCommand('up')} to record them again`,
127
+ };
128
+ }
129
+ /** Forget the record once no stack runs, so a later doctor compares nothing against a stack that is gone. */
130
+ export async function forgetStackInputs(cockpitRoot) {
131
+ try {
132
+ await rm(join(cockpitRoot, RECORD_PATH), { force: true });
133
+ return [];
134
+ }
135
+ catch (error) {
136
+ return [{
137
+ severity: 'warning',
138
+ code: 'STACK_INPUTS_UNRECORDED',
139
+ message: `the stack stopped, but ${join(cockpitRoot, RECORD_PATH)} could not be removed (${errorCause(error)}), so doctor may still compare against it`,
140
+ target: RECORD_PATH,
141
+ fix: `delete ${join(cockpitRoot, RECORD_PATH)}`,
142
+ }];
143
+ }
144
+ }
145
+ function decode(bytes) {
146
+ const value = tryParseJson(bytes.toString('utf8'));
147
+ const text = (field) => typeof field === 'string' && field.length > 0;
148
+ if (!value || typeof value !== 'object' || value.format !== FORMAT || !text(value.recordedAt) || !Array.isArray(value.siblings)
149
+ || !value.siblings.every(row => row && text(row.slug) && text(row.served) && Array.isArray(row.inputs)
150
+ && row.inputs.every((input) => input && text(input.path) && text(input.source) && (input.sha256 === null || text(input.sha256)))))
151
+ return undefined;
152
+ return value;
153
+ }
154
+ /**
155
+ * One warning per mounted sibling whose served checkout, lockfiles or declared rebuild inputs differ
156
+ * from what the running stack started with. Nothing recorded means no stack came up since the last
157
+ * `down`, and is no finding.
158
+ */
159
+ export async function staleStackDiagnostics(cockpitRoot, served) {
160
+ const path = join(cockpitRoot, RECORD_PATH);
161
+ let bytes;
162
+ try {
163
+ const read = readBoundedFile(path, RECORD_MAX_BYTES);
164
+ if (read.kind === 'too-large')
165
+ return [unrecorded(`${path} is not a record cockpit wrote`)];
166
+ bytes = read.bytes;
167
+ }
168
+ catch (error) {
169
+ if (hasErrorCode(error, 'ENOENT'))
170
+ return [];
171
+ if (hasErrorCode(error, 'ELOOP', 'EISDIR'))
172
+ return [unrecorded(`${path} is not a record cockpit wrote`)];
173
+ return [unrecorded(`${path} could not be read (${errorCause(error)})`)];
174
+ }
175
+ const record = decode(bytes);
176
+ if (!record)
177
+ return [unrecorded(`${path} is not a record cockpit wrote`)];
178
+ const findings = await Promise.all(record.siblings.map(async (sibling) => {
179
+ const current = served.get(sibling.slug);
180
+ if (!current)
181
+ return undefined;
182
+ let changes;
183
+ if (current.served !== sibling.served)
184
+ changes = [`it is now served from ${current.served}`];
185
+ else {
186
+ changes = (await mapLimit(sibling.inputs, DIGEST_CONCURRENCY, async (input) => {
187
+ let digest;
188
+ try {
189
+ digest = await digestPath(join(sibling.served, input.path));
190
+ }
191
+ catch (error) {
192
+ return `${input.path} (${input.source}) could not be read: ${errorCause(error)}`;
193
+ }
194
+ return digest === input.sha256 ? undefined : `${input.path} (${input.source}) ${digest === null ? 'was removed' : input.sha256 === null ? 'was created' : 'changed'}`;
195
+ })).filter(change => change !== undefined);
196
+ }
197
+ if (changes.length === 0)
198
+ return undefined;
199
+ return {
200
+ severity: 'warning',
201
+ code: 'STACK_INPUTS_CHANGED',
202
+ message: `the stack came up at ${record.recordedAt} with sibling ${sibling.slug} from ${sibling.served}; since then ${changes.join(', ')}, `
203
+ + 'so its services may still run the old dependencies, schema or code',
204
+ target: `sibling:${sibling.slug}`,
205
+ fix: `run ${cockpitCommand('down')} and then ${cockpitCommand('up')} so every service starts on the current files; `
206
+ + 'a service whose image installs the dependencies needs that image rebuilt before up',
207
+ };
208
+ }));
209
+ return findings.filter(finding => finding !== undefined);
210
+ }
@@ -0,0 +1,31 @@
1
+ import { type Executor, type StacksContext, type StacksInput, type StacksOutcome } from './stack-inventory-report.js';
2
+ /**
3
+ * Whose orphans a prune removes. An agent's `--apply` passes this cockpit's `[autonomy]` policy, which
4
+ * says nothing for another cockpit's stacks, so it covers this cockpit's stacks and the state folders
5
+ * earlier builds left; a person who approved the preview covers the machine.
6
+ */
7
+ export type StacksPruneScope = 'this-cockpit' | 'machine';
8
+ /** What a person or an agent approves: the exact prune a fresh observation shows, and how to apply it. */
9
+ export interface StacksPruneSession {
10
+ readonly preview: StacksOutcome;
11
+ /** The digest of the whole preview, which a person types to approve it. */
12
+ readonly digest: string;
13
+ /** Whether the scope the session applies holds nothing to remove, and whether it deletes volumes. */
14
+ readonly empty: boolean;
15
+ readonly deletesData: boolean;
16
+ apply(): Promise<StacksOutcome>;
17
+ }
18
+ /**
19
+ * Observes the machine and plans the prune of every orphaned stack and state folder. The preview lists
20
+ * them all; `apply` removes those in `scope`, after observing again and only while that part of the plan
21
+ * still has the digest it had at the preview: containers first, then networks and volumes, then the
22
+ * state that holds the port lease, then unreadable state folders. A failed step stops the prune and
23
+ * keeps the state, so the stack stays listed and the next prune resumes it.
24
+ */
25
+ export declare function beginStacksPrune(input: StacksInput, scope: StacksPruneScope, dependencies: Readonly<{
26
+ execute: Executor;
27
+ }>): Promise<StacksPruneSession | StacksOutcome>;
28
+ /** The prune of an already identified checkout's machine: what `beginStacksPrune` opens once the context is loaded. */
29
+ export declare function openStacksPrune(context: StacksContext, scope: StacksPruneScope, dependencies: Readonly<{
30
+ execute: Executor;
31
+ }>): Promise<StacksPruneSession | StacksOutcome>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `cockpit stacks prune`: plans the removal of every orphaned stack and unreadable state folder the
3
+ * inventory shows, binds the plan to a digest, and removes exactly that plan once it is approved.
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+ import { authenticatedExecutionOutcome } from '../environment-exec/execute.js';
7
+ import { cockpitCommand } from '../fix-catalogue.js';
8
+ import { sha256Hex } from '../sha256.js';
9
+ import { ENVIRONMENT_CODES } from './contract.js';
10
+ import { holderId } from './identity.js';
11
+ import { machineStackRemovalSteps } from './reclaim.js';
12
+ import { loadStacksContext, observeWithDocker, refusalStatus } from './stack-inventory-report.js';
13
+ import { removeStackState, removeStateNamespaceFolder } from './state.js';
14
+ function plannedRow({ row, resources, files }) {
15
+ const named = (kind) => resources.filter((resource) => resource.kind === kind).map(({ name }) => name);
16
+ return { cockpitId: row.cockpitId, stackId: row.stackId, checkout: row.checkout, reason: row.reason,
17
+ containers: named('container'), networks: named('network'), volumes: named('volume'), stateFiles: files.map(({ path }) => path) };
18
+ }
19
+ function planOf(observed, inScope) {
20
+ const stacks = observed.stacks.filter(({ row }) => row.verdict === 'orphaned' && inScope(row));
21
+ const folders = observed.folders.filter(({ row }) => row.verdict === 'orphaned');
22
+ // The digest binds the exact resources and file bytes, so an approval covers only what was shown.
23
+ const bound = {
24
+ stacks: stacks.map(({ row, resources, files }) => ({ cockpitId: row.cockpitId, stackId: row.stackId,
25
+ resources: resources.map(({ kind, id }) => `${kind}:${id}`), files: files.map(({ kind, path, sha256 }) => `${kind}:${path}:${sha256}`) })),
26
+ folders: folders.map(({ row, fingerprint }) => `${row.path}:${fingerprint}`),
27
+ };
28
+ return { stacks, folders, digest: sha256Hex(JSON.stringify(bound)),
29
+ deletesData: stacks.some(({ resources }) => resources.some(({ kind }) => kind === 'volume')) };
30
+ }
31
+ function otherCockpitsWarning(stacks, configPath) {
32
+ if (stacks.length === 0)
33
+ return [];
34
+ return [{ severity: 'warning', target: 'stacks',
35
+ code: 'STACKS_PRUNE_UNCONFIRMED',
36
+ message: `orphaned stacks of other cockpits are removed only with a person's approval, because this cockpit's [autonomy] policy does not speak for them; an agent's --apply leaves ${stacks.map(({ row }) => row.stackId).join(', ')}`,
37
+ fix: `a person runs ${cockpitCommand('stacks prune', { config: configPath, args: '--interactive' })} in a terminal outside CI, reads the preview and types its digest` }];
38
+ }
39
+ // Containers first, so no network or volume is still held when its removal runs.
40
+ const PRUNE_ORDER = ['container', 'network', 'volume'];
41
+ /**
42
+ * Observes the machine and plans the prune of every orphaned stack and state folder. The preview lists
43
+ * them all; `apply` removes those in `scope`, after observing again and only while that part of the plan
44
+ * still has the digest it had at the preview: containers first, then networks and volumes, then the
45
+ * state that holds the port lease, then unreadable state folders. A failed step stops the prune and
46
+ * keeps the state, so the stack stays listed and the next prune resumes it.
47
+ */
48
+ export async function beginStacksPrune(input, scope, dependencies) {
49
+ const context = await loadStacksContext(input, dependencies.execute);
50
+ return 'kind' in context ? refusalStatus(context) : openStacksPrune(context, scope, dependencies);
51
+ }
52
+ /** The prune of an already identified checkout's machine: what `beginStacksPrune` opens once the context is loaded. */
53
+ export async function openStacksPrune(context, scope, dependencies) {
54
+ const observed = await observeWithDocker(context, dependencies.execute);
55
+ if ('kind' in observed)
56
+ return refusalStatus(observed);
57
+ const thisCockpit = (row) => row.cockpitId === context.own.cockpitId;
58
+ const inScope = scope === 'machine' ? () => true : thisCockpit;
59
+ const whole = planOf(observed, () => true);
60
+ const plan = planOf(observed, inScope);
61
+ const kept = observed.inventory.stacks.filter(({ verdict }) => verdict !== 'orphaned').length;
62
+ const summary = { digest: whole.digest, deletesData: whole.deletesData,
63
+ stacks: whole.stacks.map((stack) => ({ ...plannedRow(stack), thisCockpit: thisCockpit(stack.row) })),
64
+ stateFolders: whole.folders.map(({ row }) => ({ path: row.path, reason: row.reason })), kept };
65
+ const wholeEmpty = whole.stacks.length === 0 && whole.folders.length === 0;
66
+ return {
67
+ digest: whole.digest, empty: plan.stacks.length === 0 && plan.folders.length === 0, deletesData: plan.deletesData,
68
+ preview: { status: 0, diagnostics: otherCockpitsWarning(whole.stacks.filter(({ row }) => !thisCockpit(row)), context.configPath),
69
+ result: { dryRun: true, ...summary, apply: wholeEmpty ? null : cockpitCommand('stacks prune', { config: context.configPath, args: '--apply' }) } },
70
+ apply: async () => {
71
+ const again = await observeWithDocker(context, dependencies.execute);
72
+ if ('kind' in again)
73
+ return refusalStatus(again);
74
+ const current = planOf(again, inScope);
75
+ if (current.digest !== plan.digest)
76
+ return { status: 2, result: null, diagnostics: [{ severity: 'error', target: 'stacks',
77
+ code: 'STACKS_PRUNE_UNCONFIRMED',
78
+ message: 'the orphaned stacks changed between the preview and the removal, so nothing was removed',
79
+ fix: `preview again with ${cockpitCommand('stacks prune', { config: context.configPath })} and approve the new preview` }] };
80
+ const ownerId = holderId({ stackId: context.own.stackId, checkoutId: context.own.checkoutId, sessionId: randomUUID() });
81
+ const removed = [];
82
+ const incomplete = (what) => ({ status: 2, result: { removed }, diagnostics: [{ severity: 'error', target: 'stacks',
83
+ code: 'STACKS_PRUNE_INCOMPLETE',
84
+ message: `prune stopped: ${what}; what it had not removed is still listed`,
85
+ fix: `inspect the named resource, then run ${cockpitCommand('stacks prune', { config: context.configPath, args: '--apply' })} again to resume` }] });
86
+ for (const stack of current.stacks) {
87
+ for (const kind of PRUNE_ORDER) {
88
+ const batch = stack.resources.filter((resource) => resource.kind === kind);
89
+ if (batch.length === 0)
90
+ continue;
91
+ const removals = machineStackRemovalSteps(context.docker, again.selection, batch) ?? [{ step: undefined, resources: batch }];
92
+ for (const { step, resources } of removals) {
93
+ const outcome = step === undefined ? undefined : authenticatedExecutionOutcome(await dependencies.execute(step), step);
94
+ if (outcome?.kind !== 'complete')
95
+ return incomplete(`docker could not remove ${kind} ${resources.map(({ name }) => name).join(', ')} of ${stack.row.stackId}${outcome ? ` (${outcome.code})` : ''}`);
96
+ }
97
+ }
98
+ if (stack.namespace !== undefined && stack.files.length > 0) {
99
+ const state = await removeStackState(stack.namespace, stack.row.stackId, stack.files, { ownerId });
100
+ if (state.kind !== 'complete')
101
+ return incomplete(`the state of ${stack.row.stackId} could not be removed: ${state.kind === 'refusal' ? state.message : 'it disappeared'}`);
102
+ if (state.value.kind === 'changed')
103
+ return incomplete(state.value.reason);
104
+ }
105
+ removed.push(plannedRow(stack));
106
+ }
107
+ const folders = [];
108
+ for (const { row, fingerprint } of current.folders) {
109
+ const folder = await removeStateNamespaceFolder(context.namespace, row.path, fingerprint, { ownerId });
110
+ if (folder.kind !== 'complete')
111
+ return incomplete(`${row.path} could not be removed: ${folder.kind === 'refusal' ? folder.message : 'it disappeared'}`);
112
+ if (folder.value.kind === 'changed')
113
+ return incomplete(folder.value.reason);
114
+ folders.push(row.path);
115
+ }
116
+ // The removal is reported only once a fresh observation no longer lists what was removed.
117
+ const after = await observeWithDocker(context, dependencies.execute);
118
+ if ('kind' in after)
119
+ return refusalStatus(after);
120
+ const left = planOf(after, inScope);
121
+ const remaining = removed.filter(({ cockpitId, stackId }) => left.stacks.some(({ row }) => row.cockpitId === cockpitId && row.stackId === stackId));
122
+ if (remaining.length > 0 || left.folders.some(({ row }) => folders.includes(row.path))) {
123
+ return incomplete(`a fresh inventory still lists ${remaining.map(({ stackId }) => stackId).join(', ') || 'a removed state folder'}`);
124
+ }
125
+ return { status: 0, diagnostics: otherCockpitsWarning(planOf(after, (row) => !inScope(row)).stacks, context.configPath),
126
+ result: { digest: plan.digest, removed, removedStateFolders: folders } };
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `cockpit stacks`: identifies the checkout running the command, observes the machine through the
3
+ * local Docker daemon, and lists every stack with its verdict. It changes nothing; the prune in
4
+ * `stack-inventory-prune.ts` opens from the same context.
5
+ */
6
+ import type { ExecuteProbeOutcome } from '../environment-exec/execute.js';
7
+ import { type Diagnostic, type ExecutionStep } from './contract.js';
8
+ import { type ResolvedExecutable } from './executable.js';
9
+ import { checkoutId as deriveCheckoutId } from './identity.js';
10
+ import { type Observation, type OwnCheckout, type Refusal } from './stack-inventory.js';
11
+ import { type StateNamespace } from './state.js';
12
+ export type Executor = (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
13
+ /** What `cockpit stacks` and its prune are run with: the configuration and the caller's environment. */
14
+ export interface StacksInput {
15
+ readonly configPath: string;
16
+ readonly env: Readonly<Record<string, string | undefined>>;
17
+ }
18
+ export interface StacksContext {
19
+ readonly configPath: string;
20
+ readonly namespace: StateNamespace;
21
+ readonly own: OwnCheckout & Readonly<{
22
+ checkoutId: ReturnType<typeof deriveCheckoutId>;
23
+ }>;
24
+ readonly docker: Readonly<{
25
+ executable: ResolvedExecutable;
26
+ context: string;
27
+ host?: string;
28
+ cwd: string;
29
+ env: Readonly<Record<string, string>>;
30
+ timeoutMs: number;
31
+ }>;
32
+ }
33
+ export declare function loadStacksContext({ configPath, env }: StacksInput, execute: Executor): Promise<StacksContext | Refusal>;
34
+ export declare function observeWithDocker(context: StacksContext, execute: Executor): Promise<Observation | Refusal>;
35
+ export interface StacksOutcome {
36
+ readonly status: number;
37
+ readonly result: unknown;
38
+ readonly diagnostics: readonly Diagnostic[];
39
+ }
40
+ export declare function refusalStatus(refusal: Refusal): StacksOutcome;
41
+ /** `cockpit stacks`: lists every stack this machine knows about, and changes nothing. */
42
+ export declare function runStacksInventory(input: StacksInput, dependencies: Readonly<{
43
+ execute: Executor;
44
+ }>): Promise<StacksOutcome>;
@@ -0,0 +1,78 @@
1
+ import { loadCockpitConfig } from '../config/load.js';
2
+ import { cockpitCommand } from '../fix-catalogue.js';
3
+ import { ENVIRONMENT_CODES } from './contract.js';
4
+ import { selectLocalDockerDaemon } from './docker-daemon.js';
5
+ import { resolveDockerExecutable, resolveGitExecutable } from './executable.js';
6
+ import { checkoutId as deriveCheckoutId, stackId as deriveStackId } from './identity.js';
7
+ import { discoverMachineStacks } from './reclaim.js';
8
+ import { consumeSetupCheckoutProjection, observeSetupCheckout, withSetupCheckoutProjection } from './setup-observation.js';
9
+ import { observeMachineStacks, refuse } from './stack-inventory.js';
10
+ import { selectStateRoot } from './state.js';
11
+ const STEP_TIMEOUT_MS = 10_000;
12
+ export async function loadStacksContext({ configPath, env }, execute) {
13
+ const loaded = await loadCockpitConfig(configPath);
14
+ if (loaded.kind !== 'complete')
15
+ return { kind: 'refusal', diagnostics: loaded.diagnostics };
16
+ const source = loaded.result.source;
17
+ if (process.platform !== 'darwin' && process.platform !== 'linux')
18
+ return refuse(ENVIRONMENT_CODES.STATE_ROOT_UNAVAILABLE, 'cockpit state is kept on macOS or Linux only', process.platform);
19
+ const git = await resolveGitExecutable(env);
20
+ if (git.state !== 'available')
21
+ return refuse(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, git.reason, 'git');
22
+ const observedAt = new Date().toISOString();
23
+ const observed = await observeSetupCheckout({ source, cockpitName: loaded.result.config.cockpit.name, observedAt,
24
+ env: Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === 'string')), gitExecutable: git.value });
25
+ if (observed.state === 'failed')
26
+ return refuse(observed.code, observed.reason, source.path);
27
+ const checkout = await withSetupCheckoutProjection(observed, { source, cockpitName: loaded.result.config.cockpit.name,
28
+ evaluatedAt: Date.parse(observedAt) }, consumeSetupCheckoutProjection);
29
+ if (!checkout)
30
+ return refuse(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'the checkout running this command could not be identified', source.path);
31
+ const namespace = await selectStateRoot({ platform: process.platform, home: env.HOME, xdgStateHome: env.XDG_STATE_HOME,
32
+ checkoutRoots: [checkout.currentCheckoutPath, checkout.mainCheckoutPath], cockpitId: checkout.cockpitId });
33
+ if (namespace.kind !== 'complete')
34
+ return namespace.kind === 'refusal' ? refuse(namespace.code, namespace.message, namespace.target)
35
+ : refuse(ENVIRONMENT_CODES.STATE_ROOT_UNAVAILABLE, 'the state root is unavailable', source.path);
36
+ // Prune deletes, so the daemon is fixed once, as for down and reset, and every step addresses it.
37
+ const daemon = await selectLocalDockerDaemon(env, STEP_TIMEOUT_MS, execute, { command: 'stacks',
38
+ localOnly: 'the inventory and its prune describe this machine, which a remote daemon says nothing about' });
39
+ if (daemon.kind === 'refusal')
40
+ return { kind: 'refusal', diagnostics: [daemon.diagnostic] };
41
+ const docker = await resolveDockerExecutable(env);
42
+ if (docker.state !== 'available')
43
+ return refuse(ENVIRONMENT_CODES.RUNTIME_UNAVAILABLE, docker.reason, 'docker');
44
+ return {
45
+ configPath: source.path, namespace: namespace.value,
46
+ own: { cockpitId: checkout.cockpitId, checkoutId: checkout.checkoutId, mainCheckoutPath: checkout.mainCheckoutPath,
47
+ stackId: deriveStackId({ cockpitId: checkout.cockpitId, checkoutId: checkout.checkoutId, variant: { kind: 'shared' } }) },
48
+ docker: { executable: docker.value, context: daemon.dockerContext, ...(env.DOCKER_HOST === undefined ? {} : { host: env.DOCKER_HOST }),
49
+ cwd: checkout.currentCheckoutPath, env: { PATH: env.PATH ?? '' }, timeoutMs: STEP_TIMEOUT_MS },
50
+ };
51
+ }
52
+ export async function observeWithDocker(context, execute) {
53
+ const discovery = await discoverMachineStacks(context.docker, execute);
54
+ if (discovery === undefined)
55
+ return refuse(ENVIRONMENT_CODES.RUNTIME_UNAVAILABLE, `the containers, networks and volumes cockpit labels could not be listed on Docker context ${context.docker.context}`, 'docker', 'start Docker, or select the local context that runs cockpit stacks, then retry');
56
+ return observeMachineStacks({ namespace: context.namespace, own: context.own, discovery });
57
+ }
58
+ export function refusalStatus(refusal) {
59
+ return { status: 2, result: null, diagnostics: refusal.diagnostics };
60
+ }
61
+ /** `cockpit stacks`: lists every stack this machine knows about, and changes nothing. */
62
+ export async function runStacksInventory(input, dependencies) {
63
+ const context = await loadStacksContext(input, dependencies.execute);
64
+ if ('kind' in context)
65
+ return refusalStatus(context);
66
+ const observed = await observeWithDocker(context, dependencies.execute);
67
+ if ('kind' in observed)
68
+ return refusalStatus(observed);
69
+ const orphaned = observed.inventory.stacks.filter(({ verdict }) => verdict === 'orphaned').length
70
+ + observed.inventory.stateFolders.filter(({ verdict }) => verdict === 'orphaned').length;
71
+ return { status: 0, result: observed.inventory, diagnostics: orphaned === 0 ? [] : [orphanedWarning(orphaned, context.configPath)] };
72
+ }
73
+ function orphanedWarning(count, configPath) {
74
+ return { severity: 'warning', target: 'stacks',
75
+ code: 'STACKS_ORPHANED',
76
+ message: `${count === 1 ? '1 stack or state folder on this machine is' : `${count} stacks or state folders on this machine are`} orphaned: its checkout is gone or its state cannot be read, and it still holds containers, volumes, a port lease or state`,
77
+ fix: `preview the removal with ${cockpitCommand('stacks prune', { config: configPath })}, then apply it with --apply` };
78
+ }
@@ -0,0 +1,94 @@
1
+ import { type CockpitId, type EnvironmentDiagnostic, type StackId } from './contract.js';
2
+ import type { MachineStackDiscovery, MachineStackResource } from './reclaim.js';
3
+ import { type StackStateFile, type StateNamespace } from './state.js';
4
+ type CheckoutStatus = 'present' | 'replaced' | 'missing' | 'unknown';
5
+ type StackVerdict = 'current' | 'kept' | 'orphaned';
6
+ export interface StackCheckout {
7
+ readonly path: string | null;
8
+ readonly status: CheckoutStatus;
9
+ /** Where the path, or the proof that no checkout exists, was read from. */
10
+ readonly source: string;
11
+ }
12
+ export interface StackRow {
13
+ readonly cockpitId: CockpitId;
14
+ readonly stackId: StackId;
15
+ readonly composeProject: string;
16
+ readonly checkout: StackCheckout;
17
+ readonly state: Readonly<{
18
+ status: 'readable' | 'malformed' | 'absent';
19
+ path: string | null;
20
+ reason?: string;
21
+ holders: number;
22
+ draining: boolean;
23
+ }>;
24
+ readonly containers: Readonly<{
25
+ running: readonly string[];
26
+ stopped: readonly string[];
27
+ }>;
28
+ readonly networks: readonly string[];
29
+ readonly volumes: readonly string[];
30
+ /** The port lease the stack still holds, by `[ports.bases]` name; null once released. */
31
+ readonly ports: Readonly<Record<string, number>> | null;
32
+ readonly lastActivity: Readonly<{
33
+ at: string;
34
+ source: 'state record' | 'state file modification time';
35
+ }> | null;
36
+ readonly verdict: StackVerdict;
37
+ readonly reason: string;
38
+ }
39
+ /** Another cockpit's state folder this version cannot read. */
40
+ interface StateFolderRow {
41
+ readonly path: string;
42
+ readonly cockpitId: CockpitId | null;
43
+ readonly reason: string;
44
+ readonly verdict: 'kept' | 'orphaned';
45
+ readonly why: string;
46
+ }
47
+ interface MachineStackInventory {
48
+ readonly stacks: readonly StackRow[];
49
+ readonly stateFolders: readonly StateFolderRow[];
50
+ /** Docker resources with the stack label whose other ownership labels are missing or malformed. */
51
+ readonly unattributedResources: number;
52
+ }
53
+ export interface ObservedStack {
54
+ readonly row: StackRow;
55
+ readonly namespace: StateNamespace | undefined;
56
+ readonly files: readonly StackStateFile[];
57
+ readonly resources: readonly MachineStackResource[];
58
+ }
59
+ export interface Observation {
60
+ readonly selection: MachineStackDiscovery['selection'] | undefined;
61
+ readonly inventory: MachineStackInventory;
62
+ readonly stacks: readonly ObservedStack[];
63
+ readonly folders: readonly Readonly<{
64
+ row: StateFolderRow;
65
+ fingerprint: string | undefined;
66
+ }>[];
67
+ }
68
+ export interface OwnCheckout {
69
+ readonly cockpitId: CockpitId;
70
+ readonly stackId: StackId;
71
+ readonly mainCheckoutPath: string;
72
+ }
73
+ export type Refusal = Readonly<{
74
+ kind: 'refusal';
75
+ diagnostics: readonly EnvironmentDiagnostic[];
76
+ }>;
77
+ export declare function refuse(code: EnvironmentDiagnostic['code'], message: string, target: string, fix?: string): Refusal;
78
+ /**
79
+ * Joins the state root's records with what the daemon lists, and decides each stack's checkout and
80
+ * verdict. Without `discovery` it reads state and checkouts only, as doctor does, and calls no
81
+ * unreadable state folder orphaned.
82
+ */
83
+ export declare function observeMachineStacks(input: Readonly<{
84
+ namespace: StateNamespace;
85
+ own: OwnCheckout;
86
+ discovery?: MachineStackDiscovery;
87
+ }>): Promise<Observation | Refusal>;
88
+ /** The orphaned-stack warnings of `inspectMachineStateForDoctor`, with a state that cannot be read reported as a warning. */
89
+ export declare function inspectMachineStacksForDoctor(input: Readonly<{
90
+ namespace: StateNamespace;
91
+ own: OwnCheckout;
92
+ configPath: string;
93
+ }>): Promise<readonly EnvironmentDiagnostic[]>;
94
+ export {};