@b00dea/cockpit 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -1
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +76 -1
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,760 @@
1
+ # Environment lifecycle
2
+
3
+ This page is the reference for cockpit's environment commands: what each one does, what it
4
+ refuses, and how to recover when it refuses. It is for people running cockpit on a product and for
5
+ agents working inside it. For the order to run the commands in, follow
6
+ [Bring the product up](getting-started.md#bring-the-product-up); every flag is in the
7
+ [CLI reference](cli-reference.md), and `cockpit <command> --help` prints a command's usage.
8
+
9
+ ## Output and exit codes
10
+
11
+ Add `--json` to get the result and any diagnostics as one JSON document on stdout. Without
12
+ `--json`, cockpit prints the result as readable text and diagnostics to stderr. Every refusal exits
13
+ nonzero with a named diagnostic code, and unknown flags are refused. `logs` defaults to
14
+ `--tail 100 --since 15m`.
15
+
16
+ ## Input limits
17
+
18
+ cockpit checks the size of configuration, registry and adoption files before parsing them, against
19
+ the limit defined in [`config/load.ts`](https://github.com/b0dea/cockpit/blob/main/packages/cockpit/src/config/load.ts). A file that cannot
20
+ be read, is not a regular file, is too large, or changes while cockpit reads it is refused with a
21
+ diagnostic. Make the file smaller; raising an output or subprocess limit does not change this one.
22
+
23
+ A skipped registry row produces no `init --registry` draft, because cockpit may no longer know
24
+ which repository it applies to. `registry-resolve` prints shell data you can `source` only when its
25
+ input passed validation.
26
+
27
+ ## Environment modes
28
+
29
+ `[environment]` is optional. What it declares decides which commands have anything to run.
30
+
31
+ | Declares | Mode | `sync` and `check` | Runtime commands |
32
+ |---|---|---|---|
33
+ | no `compose_files` | surfaces only | write and drift-check the harness surfaces | refuse with `ENVIRONMENT_NOT_DECLARED` |
34
+ | `environment.compose_files` | compose first | the surfaces, plus `check` reads the Compose topology | run the declared Compose files |
35
+
36
+ The runtime commands are `up`, `sleep`, `session`, `down`,
37
+ `reset`, `preflight`, `logs` and `runtime measure`. Every mode writes the harness
38
+ [surfaces](concepts.md#surface) (instructions, skills, guards, permissions, evaluator and work
39
+ orders) and checks them for [drift](concepts.md#drift). A cockpit that declares only
40
+ `[environment.machine]` still gets the machine-capacity checks in `preflight`, so it is not refused
41
+ as surfaces only. `doctor` reports an undeclared runtime as not declared, not as healthy, and its
42
+ `declarations` result says whether the runtime, the reset volumes and the seed command are
43
+ declared. [`examples/docs-only`](https://github.com/b0dea/cockpit/blob/main/examples/docs-only/README.md) and
44
+ [`examples/compose-first`](https://github.com/b0dea/cockpit/blob/main/examples/compose-first/README.md) show the two modes. Vendor stubs,
45
+ TLS, seeding and restoring data are plain Compose; [Compose patterns](compose-patterns.md) shows
46
+ each.
47
+
48
+ ### Compose-first stacks
49
+
50
+ `environment.compose_files` lists paths relative to the cockpit root, and each must stay inside it.
51
+ Compose resolves every relative path in those files, such as `build: ./api` or `"./api:/app"`,
52
+ against the directory of the first listed file, exactly as `docker compose -f <first> -f <second>`
53
+ does. So `compose_files = ["shop/compose.yml"]` runs a product repository's own file with its `./`
54
+ paths meaning what they mean in that repository. Every command passes that directory to Compose as
55
+ `--project-directory`, because Compose reads verified snapshots of the files and could not
56
+ otherwise tell where they came from. List files from two directories only when the later ones name
57
+ no relative paths, since theirs resolve against the first file's directory too.
58
+
59
+ `check` reads your Compose topology with `docker compose config`, without interpolation, and refuses
60
+ with `COMPOSE_TOPOLOGY_UNAVAILABLE` if Docker cannot produce it. It also checks:
61
+
62
+ - a published host port not written as a `[ports.bases]` variable, such as `"127.0.0.1:4200:3000"`,
63
+ fails with `COMPOSE_PORT_NOT_LEASED`, because the same number collides across worktrees and
64
+ parallel stacks; write `"127.0.0.1:${API_HOST_PORT}:3000"` instead;
65
+ - a published port with no host interface, such as `"${API_HOST_PORT}:3000"`, warns with
66
+ `COMPOSE_PORT_ALL_INTERFACES`, because Docker then binds every interface and the service is
67
+ reachable from the network; write `"127.0.0.1:${API_HOST_PORT}:3000"`, or name `0.0.0.0` when you
68
+ mean the wider exposure;
69
+ - a service with no Compose healthcheck warns with `COMPOSE_HEALTHCHECK_MISSING`, because running
70
+ is then treated as ready;
71
+ - an image referenced by tag rather than digest warns with `COMPOSE_IMAGE_UNPINNED`.
72
+
73
+ `doctor` reports a refusal from this check as itself, with its fix, not as the surface evidence it makes
74
+ unavailable. `init` shows the port and healthcheck findings beside the drafted `cockpit.toml`, as
75
+ warnings carrying the same fix; it never edits a Compose file, so apply the edit before or right after
76
+ accepting.
77
+
78
+ cockpit never edits your Compose files. To run the stack, `sync` writes `compose.cockpit.yml`, an
79
+ override that belongs to cockpit and adds its ownership labels to every service, the default
80
+ network and every named volume that is not `external`. Volumes also get a generation label, so
81
+ `reset` can account for each `resettable_volumes` entry. `check` drift-checks the override, and
82
+ runtime commands pass it after your declared files only while it matches what cockpit would
83
+ generate now. A Compose `include` is refused with `GENERATED_DECLARATION_INVALID`, because Compose
84
+ reads an included file from disk, outside the verified snapshot, and cockpit could not label the
85
+ services it brings in; list the file in `environment.compose_files` instead.
86
+
87
+ Runtime commands give Compose only these variables: the leased `[ports.bases]` variables, each
88
+ `[[sibling]] var` set to that sibling's absolute directory, and cockpit's own stack and holder
89
+ identities. Any other `${VAR}` reference needs a `:-default`. Every Compose command that
90
+ interpolates the checked configuration turns off Compose's automatic loading of the project `.env`
91
+ file, because a file cockpit has not checked must not change the topology it has checked. The
92
+ topology check does not read service `env_file` contents either, so service secrets never enter
93
+ cockpit's records; Compose still passes those files to the containers when the stack runs.
94
+
95
+ Mounts must match the declaration in both directions:
96
+
97
+ - Each sibling declared with `mount = "always"` must be bind-mounted, whole or a directory inside
98
+ it. Otherwise `check` fails with `COMPOSE_SIBLING_NOT_MOUNTED` and `up` refuses to start the
99
+ stack. Mount it through its variable, as in `"${WORKER_DIR}:/srv/worker"` or
100
+ `"${WORKER_DIR}/src:/app"`, through a path relative to the first Compose file's directory, as in
101
+ `"./worker:/srv/worker"` from a `compose.yml` in the cockpit root, or set `mount = "none"` on the
102
+ row.
103
+ - Each `environment.generated_host_paths` entry must reach a container: bound itself, through a
104
+ directory inside it, or inside a bound directory such as a sibling mounted whole. Otherwise
105
+ `check` fails with `GENERATED_HOST_PATH_UNMOUNTED`.
106
+ - A container gets only what `cockpit.toml` declares. A bind outside every `mount = "always"`
107
+ sibling and every `environment.generated_host_paths` entry, or one that leaves its sibling
108
+ through a symbolic link, fails `check` with `COMPOSE_BIND_UNDECLARED`, and `up` refuses it.
109
+
110
+ ### Scaffold a Compose file
111
+
112
+ A small app with no Compose file can get a starting one, once:
113
+
114
+ ```sh
115
+ npx --no cockpit init . --scaffold-compose api --port 3000 --with postgres,redis
116
+ ```
117
+
118
+ `--scaffold-compose` names the app directory relative to the cockpit root (`.` for the root
119
+ itself) and `--port` the port the app listens on inside its container. The app builds from its
120
+ Dockerfile; without one, a directory with a `package.json` runs on `node:22-alpine` and needs
121
+ `--command "npm start"` or whatever starts it. `--with` adds a database or cache from a fixed list,
122
+ `postgres` and `redis`, each on a pinned major version with a healthcheck, a named volume and its
123
+ URL in the app's environment. The written `compose.yml` publishes the app on
124
+ `127.0.0.1:${API_HOST_PORT:-3000}` and bind-mounts the app directory, and the command prints the
125
+ `[ports.bases]` variable and `resettable_volumes` entries to declare. `check` then warns that the
126
+ app service has no healthcheck and that the images are pinned by tag; add a healthcheck and pin
127
+ each image by digest when you can. The file is yours from then
128
+ on: cockpit runs it as written and never rewrites it, and the scaffold refuses with
129
+ `INIT_SCAFFOLD_COMPOSE_EXISTS` when the cockpit root already holds any Compose file.
130
+
131
+ ## Local URLs and port families
132
+
133
+ Write each published port with an explicit loopback binding in your Compose file, as in
134
+ `"127.0.0.1:${API_HOST_PORT}:3000"`, so starting a local stack does not expose it to the network;
135
+ use `::1` for IPv6 loopback, or `0.0.0.0` only when you intend that wider exposure. cockpit does not
136
+ rewrite your published interfaces.
137
+
138
+ Stack *n* publishes each `[ports.bases]` value plus *n* × `ports.step`, for up to `ports.count`
139
+ stacks (defaults: step 100, count 27). `up` leases a whole port family under one lock shared by
140
+ every cockpit run by your user account with the same canonical
141
+ [state root](troubleshooting.md#clear-cockpit-state). It skips any family holding a port that
142
+ another cockpit's active lease holds, then checks that no other program is listening on
143
+ the ports it picked. Cockpits with similar bases therefore never get the same ports, even before
144
+ either stack starts listening; an overlap costs a candidate family, never a shared port. Each
145
+ cockpit's stack count applies only to its own stacks. A retained lease keeps its original family
146
+ and blocks any conflicting reservation. Other user accounts and separate state roots, including different Linux `XDG_STATE_HOME`
147
+ settings, are not coordinated; only the listener check separates them.
148
+
149
+ The listener check looks for a listener on each port on IPv4 loopback when ports are first
150
+ allocated. It does not reserve the port, does not see a listener bound only to IPv6, and cannot
151
+ stop another process binding the port afterwards. If startup reports a bind failure, find the
152
+ conflicting process, resolve it, and rerun `cockpit up --config cockpit.toml`. The lease stays
153
+ reserved until cockpit has confirmed its cleanup, even when its holder is no longer running. To
154
+ change a stack's ports, see [Change a running stack](#change-a-running-stack).
155
+
156
+ ### Named local URLs
157
+
158
+ Entrypoint derivatives turn a port lease into application origins, callback URLs and other
159
+ declared addresses. Add an optional `hostname` and `scheme` to use a named local URL:
160
+
161
+ ```toml
162
+ [ports.bases]
163
+ APP_HOST_PORT = 8443
164
+
165
+ [[environment.entrypoint_derivatives]]
166
+ name = "application"
167
+ kind = "origin"
168
+ port = "APP_HOST_PORT"
169
+ path = "/"
170
+ hostname = "app.sample.localhost"
171
+ scheme = "https"
172
+ ```
173
+
174
+ Run `cockpit up --config cockpit.toml --json` and use the entrypoint it returns; the example
175
+ produces `https://app.sample.localhost:<leased-port>/`. The port comes from the selected port
176
+ family, including any nonzero offset, and a retained lease derives it the same way. Omitting
177
+ `hostname` uses `127.0.0.1`; omitting `scheme` uses `http`. Automatic numeric entrypoints keep their
178
+ plain HTTP loopback URLs. These declarations set no environment variables in the application:
179
+ configure the product's public origin and callback settings to match the returned address.
180
+
181
+ Names must be lowercase DNS subdomains of `.localhost`, with ordinary letter, digit and hyphen
182
+ labels. Arbitrary suffixes, bare `localhost`, wildcard names and embedded ports are refused.
183
+ [RFC 6761](https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3) reserves `.localhost` for
184
+ loopback; an arbitrary name such as `local.app.sample` has no such guarantee. Use a distinct
185
+ hostname for each project, because
186
+ [cookies are not isolated by port](https://www.rfc-editor.org/rfc/rfc6265.html#section-8.5).
187
+
188
+ Host clients use the host resolver, and conforming resolvers map `.localhost` names to host
189
+ loopback. Inside a container, loopback is that container's own, so a product container that calls
190
+ the host-published URL needs its own `extra_hosts` mapping to the right host gateway;
191
+ service-to-service traffic normally uses Compose service names and container ports instead.
192
+ `browser.resolver` supplies mappings only to the browser the `browser` commands launch, not to an
193
+ unrelated remote browser, MCP server or other client: put the mapping where that client's network
194
+ process runs. Browser operations then use that browser's own networking and DNS, as described in
195
+ [Work and browser](work-and-browser.md#browser-evidence).
196
+
197
+ `scheme = "https"` only declares a URL; the product's Compose stack must serve TLS on that leased
198
+ port. cockpit does not install trust roots, change host DNS or start a shared HTTPS proxy. To set
199
+ it up:
200
+
201
+ 1. Provision a certificate whose subjectAltName covers the exact hostname. Port offsets do not
202
+ change the certificate identity.
203
+ 2. Keep private keys outside tracked files and build contexts, and mount only the leaf key into
204
+ the TLS service.
205
+ 3. Explicitly trust the issuer in each runtime that connects. Clients need the public CA
206
+ certificate, never the CA signing key.
207
+ 4. Rerun `cockpit up --config cockpit.toml` and open the returned URL with certificate validation
208
+ enabled.
209
+
210
+ [Compose patterns](compose-patterns.md#serve-tls) shows a TLS service in front of the app.
211
+
212
+ ## Sibling and template paths
213
+
214
+ `sync` and `check` require each declared [sibling](concepts.md#sibling-repository) directory to
215
+ exist inside the siblings root, both as written and after following filesystem links. An absolute
216
+ sibling path is accepted only inside that root. Get missing checkouts onto disk, for example with
217
+ `cockpit setup`, before running `sync`.
218
+
219
+ `doctor` and `preflight` inspect every declared environment file in every sibling, and each check
220
+ lists all its failing paths, so one healthy file cannot hide another file's missing instance or
221
+ stale key names. Restore the named checkout or bring its instance keys in line with its template,
222
+ then rerun `cockpit doctor --config cockpit.toml`. When no sibling declares an environment file,
223
+ the instance-presence and key-freshness checks pass: there is nothing to seed or compare.
224
+
225
+ ## Lifecycle and ownership
226
+
227
+ `setup` gets the declared sibling checkouts onto disk and wires them up. `mount` controls how the
228
+ stack uses a checkout; `acquire` controls how it gets onto disk; the two are independent. Setup
229
+ looks at declared secret-file paths but never reads or copies their contents.
230
+
231
+ A sibling with `instruction_pointer = true` asks setup to connect the product's own **AGENTS.md**
232
+ to the cockpit guide. Plain `cockpit setup --config cockpit.toml` only reports the proposed path, so
233
+ an agent cannot silently change the instructions later agents load. After a person reviews the
234
+ exact block, `cockpit setup --config cockpit.toml --write-instruction-pointers` appends that marked
235
+ block and keeps every other byte. A missing guide is created only by this approved write; edited,
236
+ partial or duplicate marked blocks are refused so a person can reconcile them. Setup also refuses
237
+ redirected directories and invalid UTF-8 rather than replace the product's bytes.
238
+
239
+ An agent started inside a sibling loads only the instruction files its own tool reads: Codex and
240
+ OpenCode read **AGENTS.md**, while Claude Code reads **CLAUDE.md**, as runs of each tool beside a
241
+ decoy file showed. Every tool gets the same
242
+ instructions only when AGENTS.md holds them and CLAUDE.md imports it with a line reading
243
+ `@AGENTS.md`. `doctor` warns for every sibling laid out otherwise:
244
+ `SIBLING_INSTRUCTIONS_AGENTS_ONLY`, `SIBLING_INSTRUCTIONS_CLAUDE_ONLY`,
245
+ `SIBLING_INSTRUCTIONS_UNLINKED` (both files, no import), `SIBLING_INSTRUCTIONS_MISSING` (neither),
246
+ and `SIBLING_INSTRUCTIONS_TOOL_ONLY` for files such as **.cursorrules** or **GEMINI.md** that none of
247
+ those tools load. Setup proposes the two changes that need no judgement, and makes them only when
248
+ a person runs `cockpit setup --config cockpit.toml --write-instruction-files` after reviewing them:
249
+ a sibling with only AGENTS.md gets a CLAUDE.md holding `@AGENTS.md`, and a sibling with only
250
+ CLAUDE.md has it copied byte for byte to AGENTS.md and replaced by `@AGENTS.md`. Setup proposes no
251
+ move when CLAUDE.md imports other files with `@`, because only Claude Code would follow those
252
+ imports from AGENTS.md. It writes only ordinary files, never through a link, and refuses when a
253
+ file changed after it was read. Merging two diverging files, or writing a first AGENTS.md, needs a
254
+ person's judgement and stays with them.
255
+
256
+ ### Start or resume the stack
257
+
258
+ `up` starts the selected stack, or resumes it. It records a holder it newly adds separately from an
259
+ existing one, so if the start fails, cockpit undoes only what that attempt added. Before resuming,
260
+ cockpit checks that the saved progress still matches the current lifecycle record, runtime and port
261
+ lease. If it does, cockpit rechecks the services that already completed and resumes the rest; if
262
+ anything changed, it discards that progress before touching containers left by the earlier
263
+ attempt. After the first seed, missing saved progress blocks startup. Restore that state rather
264
+ than delete it: deleting it would make the stack look like a fresh environment when it is not.
265
+
266
+ When a service that `up` started does not become healthy, cockpit reports that service's last log
267
+ lines as one diagnostic, then stops its container and keeps it, so a restart policy cannot leave it
268
+ restarting. Failure logs are limited to that service and time window, and never follow the process
269
+ that failed. The holder and lease stay, so the next `up` resumes and `down` removes the stack.
270
+
271
+ When `up` starts services, it times the start, from the earliest recorded service start to the
272
+ moment the last newly started service reports healthy. If that exceeds `cold_start_seconds`, `up`
273
+ refuses with `MACHINE_COLD_START_EXCEEDED`. Each service start may run for the whole declared
274
+ budget, and the configuration refuses a budget above 3600 seconds, the longest `up` holds one
275
+ start open. A successful result reports `coldStart.measuredMillis`,
276
+ `limitMillis` and `headroomMillis`. A partial resume times only the services that run started.
277
+ Joining a stack that is already running, or that another holder started, reports no duration
278
+ rather than an invented one.
279
+
280
+ ### Release the stack
281
+
282
+ `sleep` removes the current holder. It stops the shared runtime only when no other holder remains,
283
+ so one holder cannot stop another's environment. Stopping the runtime also releases the stack's port
284
+ lease, so a stopped stack does not use up one of the `ports.count` families other checkouts need;
285
+ the next `up` leases afresh and may land on a different family, so read the ports and entrypoints
286
+ it returns instead of reusing the earlier ones. `stackId` names the shared runtime and
287
+ `checkoutId` names one checkout; two checkouts with the same stack ID are still not
288
+ interchangeable.
289
+
290
+ `session` runs `up`, keeps that holder while one foreground command runs, and runs `sleep` only
291
+ after confirming that the command's process group has stopped. A command that prints nothing still
292
+ counts as running. Interrupting the wrapper stops only the process group it started, then sleeps.
293
+ If cockpit cannot confirm that the process group stopped, it keeps the holder so you can clean up
294
+ explicitly. Ordinary cockpit lifecycle commands run inside the wrapper use the same private session
295
+ identity, and a second `cockpit session` wrapper is refused. Once cleanup completes, `session`
296
+ exits with the command's own status, or 128 plus the signal number (and names the signal) when a
297
+ signal ended it. It exits 2 when cleanup fails and 130 when you cancel it.
298
+
299
+ A session records its wrapper's process ID and start identity with its hold: on Linux the start in
300
+ clock ticks since boot from `/proc`, which a wall-clock step (NTP, a resumed VM) does not move, and
301
+ elsewhere the start time `ps` reports. A wrapper that dies without its closing `sleep`, from
302
+ SIGKILL, a crash or a closed terminal, leaves that hold behind; the next `up`, `sleep`, `down` or
303
+ `reset` from the checkout sees that the process is gone, or that its process ID now belongs to a
304
+ process with another start, and releases it. When it was the last hold, the command's own hold
305
+ takes the running stack over, so `sleep` stops it and `down` removes it. A hold whose process is
306
+ still running is kept, and so is one whose start could not be read while its process ID is in use:
307
+ the `CLAIM_CO_TENANT` refusal names each such holder and its process ID, so you can end that
308
+ command, for example with `kill <pid>`, and retry.
309
+
310
+ The wrapper passes its process ID and start identity to its command in `COCKPIT_SESSION_PROCESS`, so
311
+ a hold that a `sleep` and then an `up` inside the session drop and re-add is still tied to the
312
+ wrapper, and is released the same way once the wrapper is gone. `up` reads that process's start
313
+ again before recording it, and refuses with `CLI_USAGE_INVALID` when the wrapper has ended or the
314
+ value is malformed.
315
+
316
+ The command `session` runs gets the stack's addresses in its environment, so an agent started under
317
+ it can reach the stack without rerunning `up --json`:
318
+
319
+ - each `[ports.bases]` name holds the port leased for this stack, the same value the Compose files
320
+ read, as in `API_HOST_PORT=4210`;
321
+ - each entrypoint `up --json` returns is under `COCKPIT_ENTRYPOINT_` followed by its name in upper
322
+ case with hyphens as underscores, as in
323
+ `COCKPIT_ENTRYPOINT_API_HOST_PORT=http://127.0.0.1:4210/`.
324
+
325
+ The lease is the truth for the stack, so these replace a value of the same name already set in the
326
+ calling shell. They are read once when the command starts and do not follow a later `down` and
327
+ `up`. When the command ends, the session releases only its own hold: a stack that an earlier `up`
328
+ from this checkout still holds keeps running, and the session says so with
329
+ `SESSION_STACK_RETAINED`; `sleep` releases that hold.
330
+
331
+ Each `sleep` and `down` stops containers with a stop grace: Docker sends SIGTERM and waits ten
332
+ seconds before killing. A service whose PID 1 ignores SIGTERM, as `node` does unless the program
333
+ handles it, pays the whole grace at every stop. Add `init: true` to such a service in your Compose
334
+ file, or handle SIGTERM in the program.
335
+
336
+ ### Remove the stack and its data
337
+
338
+ `down` removes only what cockpit's runtime, checkout and ownership records show it owns: it lists
339
+ only containers, networks and images that carry this stack's ownership label, and reports and
340
+ keeps anything it cannot show is this stack's. Ownership is the stack's identity, not the
341
+ configuration, so a container or network that an earlier `cockpit.toml` started is removed too. An
342
+ image the stack built carries the same label and is removed, unless it also has a second tag.
343
+ Volumes stay, because only `reset` deletes data.
344
+
345
+ When one of the stack's own resources has to stay, such as a network another container is still
346
+ attached to or an image tagged under a second name, `down` refuses with `DOWN_RESOURCES_KEPT` and
347
+ names each resource and the reason, rather than reporting a stack removed that is still there. It
348
+ clears startup progress only after confirming the removals, which lets the next `up` start
349
+ afresh. `down` also retires a stack that `sleep` or an ended `session` left stopped:
350
+ with no holder left, it claims the stack for this checkout without starting anything and removes
351
+ it, so you do not have to run `up` first. While another session still holds the stack, `down`
352
+ refuses with `CLAIM_CO_TENANT` and names that session's holder and process; end that
353
+ `cockpit session`, then retry.
354
+
355
+ `reset` first shows an exact preview of the declared volumes and generated host paths it would
356
+ delete. Plain and `--json` reset calls delete nothing. Interactive reset needs a person at a
357
+ terminal outside CI; the approval stays with the process that showed the preview, which checks
358
+ everything again before it acts. `down` and `reset` delete only from a local Docker daemon, which
359
+ they fix once per run; see [Choose the Docker daemon](#choose-the-docker-daemon).
360
+
361
+ Name each `environment.resettable_volumes` entry by its top-level `volumes:` key in the Compose
362
+ file. Docker's volume name carries a per-checkout project prefix, so it is not accepted. `sync`,
363
+ `check` and every runtime command, `down` included, refuse a name that no declared volume has, and
364
+ an `external` volume too, because cockpit cannot label it. A declared volume that does not exist yet is left out of the
365
+ preview. A volume that an earlier `cockpit.toml` created is still this stack's data, and `reset`
366
+ deletes it with the rest.
367
+
368
+ Every destructive action is recorded durably before it starts. If cockpit itself is interrupted, a
369
+ detached supervisor process keeps track of that exact attempt. The next `sleep`, `down` or approved
370
+ `reset` asks the supervisor to stop the action's process group, confirms the group is gone, marks
371
+ the attempt as failed, and retries the saved action. If the target was already removed, cockpit
372
+ checks again that it is gone and that the remaining targets are unchanged, then marks the action
373
+ complete without repeating it. `DRAIN_ATTEMPT_REPAIR_REQUIRED` means the attempt marker, supervisor
374
+ identity, control socket or stop confirmation did not match; cockpit then keeps the attempt and
375
+ refuses any overlapping cleanup. Do not remove its state or runtime resources by hand: that
376
+ destroys the records cockpit needs to repair it safely.
377
+
378
+ ### Change a running stack
379
+
380
+ A stack's identity, carried in its ownership labels and saved progress, is a digest of the Compose
381
+ files and of the `cockpit.toml` declarations that reach its containers, not of the bytes of
382
+ `cockpit.toml`. Editing a sibling's
383
+ `purpose`, the guards, MCP, surfaces, comments or formatting leaves a running stack alone, and the
384
+ next `up` resumes it. So do the fields the host checks rather than the containers receive:
385
+ `[environment.machine]` (including `cold_start_seconds`), `addressing`, `authentication`,
386
+ `posture_requirements`, `resettable_volumes`, `seed` and `entrypoint_derivatives`.
387
+
388
+ A change to a Compose file, or to ports, mounts or anything else in `cockpit.toml` the containers
389
+ receive, gives the stack a new identity, and `up` refuses with `LIFECYCLE_PROGRESS_INVALID` rather than replace the
390
+ running stack in place. To apply such a change, for example a `[ports.bases]` edit:
391
+
392
+ 1. Edit `cockpit.toml`. This can happen while the stack runs.
393
+ 2. Have a person run `cockpit sync --config cockpit.toml` in a terminal. Until then, the agent
394
+ guard holds every tool call, because the configuration no longer matches what it was generated
395
+ for, and `sleep` and `down` refuse because they read the Compose files `sync` publishes.
396
+ 3. Run `cockpit down --config cockpit.toml`. It finds the stack and its port lease by the stack,
397
+ not by the edited declaration, and removes the containers still running the old declaration.
398
+ While another holder, such as a running `cockpit session`, still holds the stack, it refuses
399
+ with `CLAIM_CO_TENANT`.
400
+ 4. Run `cockpit up --config cockpit.toml --json` and use the newly returned ports and entrypoints.
401
+
402
+ Restoring `cockpit.toml` to the exact bytes `sync` last saw, for example with
403
+ `git restore cockpit.toml`, clears the guard's hold without a `sync`: the guard compares the file's
404
+ content, not which file holds it.
405
+
406
+ ## Choose the Docker daemon
407
+
408
+ Every command uses the daemon Docker itself would select: a `DOCKER_HOST` when set, otherwise the
409
+ context `DOCKER_CONTEXT` names, otherwise the active context `docker context show` reports. Three
410
+ commands fix that daemon once, before their first step, and address only it:
411
+
412
+ | Command | Why it fixes the daemon |
413
+ |---|---|
414
+ | `down`, `reset` | they delete; reading the context per step could list one daemon and delete from another if `docker context use` ran mid-command |
415
+ | `runtime measure` | the measurement must describe the daemon `up` will use, not one selected while the probe ran |
416
+
417
+ None of them needs a prefix, so they run as printed:
418
+
419
+ ```sh
420
+ npx --no cockpit down --config cockpit.toml
421
+ npx --no cockpit runtime measure --config cockpit.toml --image node:22-alpine
422
+ ```
423
+
424
+ However the daemon was chosen, its endpoint must be an absolute `unix://` socket path. TCP, SSH and
425
+ other transports are refused, because a remote daemon could receive a deletion and its behaviour
426
+ says nothing about this machine. A `DOCKER_HOST` that is not local is refused with
427
+ `CLI_USAGE_INVALID`; a named or active context that is not local, or whose endpoint cannot be read,
428
+ is refused with `RUNTIME_UNAVAILABLE`, naming the context and its endpoint. For `down` and `reset`,
429
+ conflicting selections are refused, and so is a TLS override. Socket paths keep literal spaces and
430
+ punctuation, and cockpit uses the exact spelling when it binds the context and runs discovery or
431
+ deletion.
432
+
433
+ ## Compose project name
434
+
435
+ Every command that runs Docker Compose passes `--project-name <cockpit.name>-<stack>`, where `<stack>` is the first 12 hex digits of the stack ID. The
436
+ stack ID comes from the cockpit's main checkout and the current checkout, so two cockpits that share
437
+ a `cockpit.name`, or two checkouts of one cockpit, get separate projects on one Docker daemon
438
+ instead of replacing each other's containers. It never depends on the bytes of `cockpit.toml`, so
439
+ editing the file keeps the same project.
440
+
441
+ Containers, networks and named volumes carry that name: a cockpit named `shop-cockpit` runs
442
+ containers such as `shop-cockpit-3f2b3aead733-api-1` and a volume
443
+ `shop-cockpit-3f2b3aead733_postgres-data`. Your own file's `name:` is overridden. To look at the stack with Docker directly, find the project with `docker compose ls`
444
+ and name it: `docker compose --project-name shop-cockpit-3f2b3aead733 ps`. Renaming `cockpit.name`
445
+ addresses a different project and stack, so run `down` before renaming, as
446
+ [Rename a cockpit](getting-started.md#rename-a-cockpit) describes.
447
+
448
+ ## Measure the container runtime
449
+
450
+ `up`, `session`, `down` and `reset` refuse with `RUNTIME_UNMEASURED` unless the container runtime
451
+ Docker selects has been measured on this machine. `sleep` and `logs` act on a running stack and
452
+ fail while its runtime no longer matches a measurement, for example after an engine upgrade.
453
+ `preflight` and `doctor` only report an unmeasured runtime, as an unavailable check. A measurement
454
+ is a fact about one machine, not something the team declares: teammates run different runtimes and
455
+ versions, so it is never written to `cockpit.toml`.
456
+
457
+ cockpit identifies the runtime from one `docker info` reading of `OperatingSystem`, `Name` and
458
+ `OSType`:
459
+
460
+ | Runtime identity | Recognised by | Provenance |
461
+ |---|---|---|
462
+ | `docker-desktop` | `OperatingSystem` is `Docker Desktop` | vendor `docker info` output, not probed |
463
+ | `orbstack` | `OperatingSystem` is `OrbStack` | observed on an OrbStack host |
464
+ | `colima` | `Name` starts with `colima` | vendor `docker info` output, not probed |
465
+ | `rancher-desktop` | `Name` ends with `rancher-desktop` | vendor `docker info` output, not probed |
466
+ | `docker-engine` | a Linux host whose daemon reports `OSType` `linux` | vendor `docker info` output, not probed |
467
+
468
+ Any other reading, including an unrecognised VM on macOS, is not identified and never accepted; the
469
+ refusal lists the raw `docker info` values so you can report them.
470
+
471
+ Measure the runtime on this machine:
472
+
473
+ ```sh
474
+ docker pull node:22-alpine
475
+ ./node_modules/.bin/cockpit runtime measure --config ./cockpit.toml --image node:22-alpine
476
+ ```
477
+
478
+ The command runs a probe in a throwaway directory: a candidate stack that exercises profiles, bind
479
+ mounts in both directions, labels, health, logs, recovery and teardown, next to a deliberately
480
+ broken Compose file that the runtime must reject. It uses only the local image you name and never
481
+ pulls. Its Compose project is `<cockpit.name>-probe-<id>`, so anything it leaves on a shared daemon
482
+ names the cockpit that ran it, and the probe removes it before returning. If a measure was killed
483
+ before it could, the next `runtime measure` first removes that cockpit's own probe containers,
484
+ network and volume and the stale `cockpit-runtime-measure-*` temporary directory, and lists what it
485
+ removed. Each probe resource carries a `dev.b0dea.cockpit.probe-owner` label naming the measure
486
+ process that created it, so a measure running from another worktree or terminal, with its own
487
+ temporary directory, is still recognised: only resources whose process is gone are removed, and
488
+ anything that may belong to a measure still running is left and named. It refuses to probe when a
489
+ leftover cannot be removed, naming it. The probe runs only from
490
+ an installed `@b00dea/cockpit` package and only against a daemon that passes the
491
+ [local endpoint rules](#choose-the-docker-daemon).
492
+
493
+ If the candidate passes and the broken file is rejected, the command records the measurement in
494
+ `machine/runtime-measurements.json` under cockpit's per-user state root (the same root the stack
495
+ state lives in), so every checkout and git worktree on this machine reads it and measures once. The
496
+ record is keyed by the runtime identity and the daemon name `docker info` reports, and holds the
497
+ engine version and the Compose version the probe saw, which becomes the Compose floor. Measuring
498
+ again replaces the row for the same runtime and daemon and keeps the others, so switching between
499
+ two runtimes needs one measurement each. A failed measurement records nothing.
500
+
501
+ `up` admits the runtime only when this machine's record has a row for the same runtime and daemon,
502
+ with the same engine version and a Compose version at or above the floor, so an engine upgrade
503
+ needs a new measurement. Recording a measurement changes no committed file, so an agent can run it
504
+ before its first `up` without waiting for a human `sync`. `doctor` names the measurement that
505
+ admitted the runtime in the `RUNTIME_UNMEASURED` check's source.
506
+
507
+ ## Secret sources and environment schemas
508
+
509
+ Keep each product component's `.env.schema` tracked beside its application. If its ignore rules
510
+ match `.env.*`, explicitly allow `.env.schema` and confirm that Git tracks it before adoption. The
511
+ schema declares variable names, validation and how a provider resolves each value; review its
512
+ resolver code and dependency lock as you would product code.
513
+
514
+ Put encrypted resolver expressions or external-provider references in
515
+ `env/.secrets/<cockpit-project>/<component>`, which the generated guards keep agents from changing
516
+ or staging. Ignore `env/.secrets/` in the cockpit's `.gitignore` before any secret material exists,
517
+ create the directories with mode 0700 and no symbolic links, and create resolver files with mode
518
+ 0600 through your own approved provisioning process. cockpit does not create those files or their
519
+ values.
520
+
521
+ The shipped setup procedure explains how to adopt Varlock, optionally, through the product's
522
+ pinned dependency and an existing declared command or script. Use agent-mode validation for status
523
+ and `run --inject vars --redact-stdout` for a trusted product process, and keep resolved
524
+ credentials out of the agent's environment. Give both paths as absolute paths with repeated
525
+ `--path`: the component schema root first, then its central resolver directory in the cockpit; the
526
+ later path wins. Installation, initialization, provider login, encryption and migration need
527
+ explicit permission from whoever owns the product and the provider. Current commands and
528
+ limitations are in the [Varlock CLI reference](https://varlock.dev/reference/cli/load-and-run/) and
529
+ [AI tools guide](https://varlock.dev/guides/ai-tools/).
530
+
531
+ `cockpit preflight` and `cockpit doctor` check every sibling that contains a `.env.schema` and skip
532
+ the rest. Each runs `varlock load --agent --format json-full --path <sibling>` from your `PATH`
533
+ with the command's timeout and a 256 KiB output cap, and keeps only key names and a validity
534
+ category; values and Varlock's messages are discarded. `ENV_SCHEMA_UNHEALTHY` fails the row and
535
+ names the sibling and its keys, or reports the row as unavailable when Varlock times out, exits
536
+ without a readable report, or a provider cannot resolve a value; run `varlock load` in that sibling
537
+ to see why. `ENV_SCHEMA_TOOL_MISSING` is a warning, not a failure, when Varlock is not installed
538
+ (<https://varlock.dev>).
539
+
540
+ Report only variable names, validation state, exit status and where each value came from. Raw
541
+ resolver output, resolved values and browser state do not belong in work-order results. Generated
542
+ guards refuse changes and staging under secrets directories, including staging a parent directory
543
+ of the central store; whether a harness can natively block reads of these files has not been
544
+ tested. Git ignore rules, filesystem permissions, encryption and output redaction do not protect
545
+ secrets from malicious code running as the same user, and trusted product child processes receive
546
+ plaintext variables. A stronger boundary needs a separately tested sandbox and provider policy.
547
+
548
+ ## Check and sync
549
+
550
+ `check` works out what the generated files should contain now and compares that with every whole
551
+ file and shared-file region listed in the ownership manifest (see
552
+ [generated ownership](concepts.md#cockpit-generated-ownership)). It reports drift and never changes
553
+ an output. `sync --dry-run` does the same preparation and lists the writes and removals, in order,
554
+ without changing anything. `sync` writes the full set as one recoverable filesystem transaction.
555
+
556
+ Team package metadata is prepared separately: a known surface conflict, an input overlap or a
557
+ refused stale-removal approval leaves it unchanged; resolve the reported conflict, then run
558
+ `cockpit sync --config cockpit.toml` again. Metadata is replaced only after these checks pass, but
559
+ a later I/O failure or concurrent input change can still leave those metadata updates in place
560
+ while the generated-file transaction reports its own recovery result.
561
+
562
+ Removing a stale file needs a separate approval of the exact path, operation and current bytes that
563
+ the dry run showed. Plain `sync` refuses to apply it; `sync --interactive` shows the request and
564
+ asks you to type its exact digest. After approval and a final recheck, cockpit can remove an
565
+ unchanged whole file, or its own region in a shared file. Files cockpit did not write,
566
+ and bytes outside its regions, are kept. A stale path that changed or is unsafe needs manual
567
+ reconciliation before you retry.
568
+
569
+ Generated content for a shared Markdown region is rejected before writing if it contains either
570
+ region delimiter, and shared JSON updates keep the whitespace outside cockpit's property group. The
571
+ transaction plans its helper steps before staging and keeps time in reserve to roll back if forward
572
+ work reaches its deadline. If cockpit cannot confirm that writing or rollback finished, it keeps the
573
+ recovery inputs and refuses to report success.
574
+
575
+ Capability states, overlay rules, MCP approval, sibling guide copies, the skill contract and
576
+ `selftest` for the generated harness surfaces are in [Harness surfaces](harness-surfaces.md);
577
+ see [Upgrade cockpit](harness-surfaces.md#upgrade-cockpit) before changing the installed version.
578
+
579
+ ## Bring-up order
580
+
581
+ Bring-up starts from the lifecycle history: `setup` writes `started`, and so does the first `up` on
582
+ a stack that has neither lifecycle data nor history, so you do not have to run `setup` first.
583
+ Lifecycle data without history is refused as lost history. Bring-up then checks the runtime,
584
+ starts the Compose stack, waits for every service to report healthy, records `seeded` in the
585
+ lifecycle history, and only then runs `environment.seed` when it is due. A refusal keeps the
586
+ progress so the next `up` resumes. Fix the named prerequisite or failed check; never edit
587
+ lifecycle state to fake `started` or `seeded` history.
588
+
589
+ ## Result words
590
+
591
+ Environment results use four words with exact meanings. `enforced` and `unavailable` mean what they
592
+ mean for harness features (see [Capability states](harness-surfaces.md#capability-states)); for the
593
+ environment:
594
+
595
+ - **enforced**: cockpit refuses a declaration, file, ownership record, identity, mount or
596
+ operation that does not match.
597
+ - **measured**: a probe produced the stated result. Repository tests run mounted candidates next to
598
+ unmounted and incompatible controls, but those synthetic controls say nothing about your local
599
+ runtime.
600
+ - **unavailable**: cockpit could not get or verify the required result, and it counts as not
601
+ passing.
602
+ - **human_needed**: product or vendor behavior a maintainer still has to review; no check has
603
+ established it.
604
+
605
+ ## Demo data, test users and roles
606
+
607
+ This section is for whoever makes the local stack start with useful data and test users.
608
+ cockpit runs the product's own seed command and records when it last succeeded. It never writes
609
+ rows or creates identities itself: the product's writers already encode its invariants, and a
610
+ second writer in cockpit would drift from them.
611
+
612
+ **What to declare.** Name the Compose volumes that hold the data, and the command that fills them:
613
+
614
+ ```toml
615
+ [environment]
616
+ compose_files = ["compose.yml"]
617
+ resettable_volumes = ["postgres-data"]
618
+ seed = { argv = ["npm", "run", "db:seed"], timeout_seconds = 300 }
619
+ ```
620
+
621
+ `up` runs the seed once every service is healthy, whenever no successful seed is recorded for the
622
+ stack or one of the `resettable_volumes` was created after the last one: the first `up`, the `up`
623
+ after `reset`, and the `up` after anything else removed the volume. The command runs on the host
624
+ from the cockpit directory, with the leased `[ports.bases]` variables and each
625
+ `COCKPIT_ENTRYPOINT_*` address added to the caller's environment, so it reaches the stack as a
626
+ person's shell would. `up --json` reports `seed` as `ran` or `current`. A seed that exits nonzero,
627
+ or runs longer than `timeout_seconds`, refuses with `DATA_SEED_FAILED`: the stack stays up, its data
628
+ may be partly applied, and the next `up` runs the seed again, so write it to be rerun. When Docker
629
+ cannot say when the volumes were created, `up` refuses with `DATA_SEED_UNAVAILABLE` rather than
630
+ guess. `seed` requires `resettable_volumes`, because those volumes are what tells cockpit the data
631
+ is gone. To seed from inside the stack instead, see
632
+ [Compose patterns](compose-patterns.md#seed-or-restore-data).
633
+
634
+ Preview a reset with `cockpit reset`, which lists exactly which volumes and host paths it will remove.
635
+ Applying it wipes data, so it needs a person by default: `reset --interactive` in a terminal, or
636
+ `reset --apply` when `[autonomy] reset` allows the caller. The next `up` seeds again, because the
637
+ volume `reset` recreated is newer than the last seed.
638
+
639
+ ```sh human-only: resets data and requires the declared local stack
640
+ ./node_modules/.bin/cockpit up --config cockpit.toml
641
+ ./node_modules/.bin/cockpit reset --config cockpit.toml --json
642
+ ./node_modules/.bin/cockpit reset --interactive --config cockpit.toml
643
+ ./node_modules/.bin/cockpit up --config cockpit.toml
644
+ ```
645
+
646
+ **Test users.** cockpit has no user model; the product's seed creates them. Give each test user a
647
+ stable synthetic ID such as `auth-admin` or `auth-member`, a role, one operation it must be allowed
648
+ and one it must be denied. Stable IDs let a re-seed update the same rows, and the allowed and denied
649
+ pair proves the roles work, not only that a sign-in succeeded. The shipped `cockpit-setup` skill
650
+ carries an on-demand reference (`references/test-data-and-personas.md`) that keeps these identities
651
+ separate from what an identity provider owns.
652
+
653
+ **Fixture hygiene.** These rules come from seeders that broke in real use:
654
+
655
+ - Seed through the product's own writers, not raw inserts, so derived fields and constraints hold.
656
+ - A re-seed restores only the fields a fixture names. Name every field a test changes, or the next
657
+ run fails its own precondition. Unset fields rather than writing null.
658
+ - Seed the settled state a test needs. Default column values can read as work in flight and
659
+ disable the interface.
660
+ - Before a seed writer runs, assert that every database and service target is local. A development
661
+ fixture is no permission to reach a shared or non-local environment.
662
+ - Resolve a sibling fixture path by probing the declared sibling root and a documented fallback;
663
+ never embed one checkout layout as a constant.
664
+ - Clean up by ownership, never by time window.
665
+
666
+ ## Declare environment postures
667
+
668
+ Declare which local requirements depend on how the product is addressed (`addressing`) and how
669
+ sign-in works locally (`authentication`). For example:
670
+
671
+ ```toml
672
+ [environment]
673
+ addressing = "hostname"
674
+ authentication = "bypassed"
675
+ posture_requirements = [
676
+ { requirement = "local-route-resolution", addressing = "hostname" },
677
+ { requirement = "local-session-access", authentication = "bypassed" },
678
+ ]
679
+ ```
680
+
681
+ A product with no local sign-in declares `authentication = "none"`; no requirement needs a tag for
682
+ it, and a requirement tagged with a sign-in value fails preflight. A plain localhost stack reached
683
+ through leased ports declares:
684
+
685
+ ```toml
686
+ [environment]
687
+ addressing = "port"
688
+ authentication = "none"
689
+ posture_requirements = [{ requirement = "leased-localhost-ports", addressing = "port" }]
690
+ ```
691
+
692
+ Use your actual requirement names. Each tag must match the selected value, and the selected
693
+ `addressing` value, and any `authentication` value other than `none`, must each appear in at least
694
+ one tag. Preflight refuses missing declarations, mismatched tags and selected values that no tag
695
+ uses. Draft configurations may leave these fields out, but then cannot pass that gate. The check
696
+ validates the declared tags; deciding which requirements need tags is still a maintainer's review.
697
+
698
+ ## Health and evidence
699
+
700
+ `preflight` is the blocking gate; `doctor` reports the same checks without repairing anything.
701
+ Before the first bring-up (`STATE_UNSEEDED`), while the stack sleeps
702
+ (`STACK_NOT_RUNNING`), and for a cockpit that declares no runtime (`ENVIRONMENT_NOT_DECLARED`,
703
+ `doctor` only), both still run every check that needs no running stack, such as Git state, sibling
704
+ presence, the machine floor and the runtime measurement, and report the checks that need running
705
+ services as not applicable; `preflight` never passes in those states. A check can pass, fail, be
706
+ unavailable, or not apply to the declared setup, and an unavailable required check never becomes a
707
+ pass because another check succeeded. Host capacity and runtime health need complete results from processes cockpit itself ran; when those results are missing, the
708
+ result does not pass.
709
+
710
+ `preflight` and `doctor` read current results without changing anything; `doctor` also previews
711
+ generated surfaces to list overrides. They never run `sync`, seed data or advance lifecycle
712
+ progress. Only bring-up runs those steps, as [Bring-up order](#bring-up-order) describes.
713
+
714
+ `doctor` exits 0 with a report whenever it can read `cockpit.toml`, whatever the verdict, and exits
715
+ 2 when the configuration is missing or invalid. Its human output is one line per area followed by
716
+ the diagnostics; `--json` carries the checks, declarations, override inspection and whether the
717
+ generated files are current. `check --json` shows the generated-file detail.
718
+
719
+ What the checks compare:
720
+
721
+ - **Git remotes.** A declared sibling remote is compared with the remote selected by that
722
+ checkout's branch upstream; cockpit does not rewrite Git remotes. A checkout with no upstream has
723
+ nothing to compare, and the result says so. Once an upstream is selected, it must be available
724
+ and match; a missing or diverged upstream blocks the gate. Only an https or ssh `remote` has a
725
+ repository identity to compare. A `remote` that is a local path (not a URL or `host:path`) is
726
+ still cloned by `setup`, resolved against the siblings root, but `preflight` refuses it with a
727
+ reason: declare the hosted https or ssh remote, or remove `remote` and set `acquire = "none"`.
728
+ `init` never proposes a local-path remote.
729
+ - **Surfaces.** Generated files must match the ownership manifest and stay within the guide line
730
+ cap and the built-in harness skill caps. Custom outputs and plugin bundle copies are checked for
731
+ being current, but no harness-side cap on their always-loaded size applies to them. Whether each
732
+ harness enforces the files is recorded in the [capability matrix](capability-matrix.md). Missing
733
+ or edited generated files block health: run `cockpit sync --config cockpit.toml`, then retry.
734
+ - **Grants.** Directory grants are checked only when a sibling declares `mount = "always"`.
735
+ - **Mounts and ports.** A runtime with no bind mounts passes as an exact empty mount comparison;
736
+ declared binds must match the current mounts exactly. Health on a running stack rechecks the
737
+ retained lease, current container identities, mounts and published listeners without reserving
738
+ sockets. Compose reports a `::` publication beside each `0.0.0.0` one, and some runtimes forward
739
+ only IPv4 loopback, so the `::` row is not required to listen while its `0.0.0.0` twin does; a
740
+ leased port that nothing publishes must stay free on both address families.
741
+ - **Capacity.** Cold-start timing applies only to services that `up` actually started; read-only
742
+ health checks do not invent a new duration. The machine disk floor compares current free space
743
+ with an explicitly declared machine floor, independently of generated file sizes; without that
744
+ declaration, host-floor checks do not apply.
745
+
746
+ `logs` reads one declared service from the selected running stack. `--tail` accepts 1 through 10000
747
+ records, and `--since` limits the time range. Records keep their stream and restart details. A read
748
+ without `--follow` has ten seconds and reports a timeout as a failure. `--follow` collects until you
749
+ interrupt it, the container stops, or two hours pass; interrupting it in the CLI returns exit 130
750
+ with the records collected before the process group stopped, and programmatic callers must supply an
751
+ abort signal. Output is limited to 1 MiB of the container's stdout and 64 KiB of its stderr. Past
752
+ that, `logs` keeps the newest whole records that fit, sets `truncated` to `true` in the JSON result
753
+ and warns with `LOGS_TRUNCATED`; lower `--tail` to stay under it. Output is returned after
754
+ collection, including structured records in JSON mode, rather than streamed.
755
+
756
+ ## Where the decisions live
757
+
758
+ Contributors looking for the source module behind each step should start at
759
+ [Architecture](architecture.md#runtime-behavior) and the
760
+ [environment code map](https://github.com/b0dea/cockpit/blob/main/AGENTS.md#environment-code-map).