@b00dea/cockpit 0.1.0 → 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 (196) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +3 -0
  3. package/cockpit.schema.json +73 -3
  4. package/dist/autonomy.d.ts +3 -2
  5. package/dist/autonomy.js +30 -13
  6. package/dist/browser/commands.d.ts +57 -2
  7. package/dist/browser/commands.js +114 -10
  8. package/dist/browser/runtime.d.ts +1 -0
  9. package/dist/browser/runtime.js +4 -1
  10. package/dist/browser/surfaces.d.ts +7 -7
  11. package/dist/browser/surfaces.js +5 -0
  12. package/dist/canonical-json.d.ts +2 -0
  13. package/dist/canonical-json.js +5 -0
  14. package/dist/cli-main.d.ts +40 -3
  15. package/dist/cli-main.js +241 -62
  16. package/dist/command-table.d.ts +19 -11
  17. package/dist/command-table.js +190 -38
  18. package/dist/config/load.d.ts +16 -4
  19. package/dist/config/load.js +25 -3
  20. package/dist/config/unconfirmed-toml.d.ts +9 -0
  21. package/dist/config/unconfirmed-toml.js +29 -0
  22. package/dist/diagnostics.js +10 -1
  23. package/dist/docs/check.d.ts +16 -0
  24. package/dist/docs/check.js +197 -0
  25. package/dist/docs/decision-record.d.ts +34 -0
  26. package/dist/docs/decision-record.js +135 -0
  27. package/dist/docs/decisions.d.ts +16 -0
  28. package/dist/docs/decisions.js +103 -0
  29. package/dist/docs/markdown.d.ts +36 -0
  30. package/dist/docs/markdown.js +88 -0
  31. package/dist/docs/references.d.ts +32 -0
  32. package/dist/docs/references.js +148 -0
  33. package/dist/environment/application/confirm.d.ts +35 -0
  34. package/dist/environment/application/confirm.js +122 -0
  35. package/dist/environment/application/destructive.js +6 -2
  36. package/dist/environment/application/doctor.d.ts +12 -7
  37. package/dist/environment/application/doctor.js +20 -8
  38. package/dist/environment/application/guard.d.ts +12 -6
  39. package/dist/environment/application/guard.js +87 -32
  40. package/dist/environment/application/health.d.ts +14 -5
  41. package/dist/environment/application/health.js +8 -2
  42. package/dist/environment/application/lifecycle-context.d.ts +10 -3
  43. package/dist/environment/application/lifecycle-state.js +4 -3
  44. package/dist/environment/application/lifecycle.d.ts +2 -1
  45. package/dist/environment/application/lifecycle.js +11 -3
  46. package/dist/environment/application/services.d.ts +60 -0
  47. package/dist/environment/application/services.js +89 -0
  48. package/dist/environment/application/siblings.d.ts +43 -0
  49. package/dist/environment/application/siblings.js +269 -0
  50. package/dist/environment/claims.d.ts +10 -10
  51. package/dist/environment/claims.js +3 -11
  52. package/dist/environment/compose-topology.d.ts +15 -7
  53. package/dist/environment/compose-topology.js +100 -23
  54. package/dist/environment/contract.d.ts +42 -9
  55. package/dist/environment/contract.js +33 -0
  56. package/dist/environment/executable.d.ts +12 -0
  57. package/dist/environment/executable.js +46 -23
  58. package/dist/environment/generated-application/check.js +2 -0
  59. package/dist/environment/generated-application/prepare.js +2 -0
  60. package/dist/environment/generated-application/shared.d.ts +10 -2
  61. package/dist/environment/generated-application/shared.js +6 -1
  62. package/dist/environment/generated-application/surface-outputs.js +23 -12
  63. package/dist/environment/generated-application/surfaces.js +16 -11
  64. package/dist/environment/generated-application/sync.js +19 -4
  65. package/dist/environment/git-observation.d.ts +25 -6
  66. package/dist/environment/git-observation.js +37 -28
  67. package/dist/environment/health-composition.d.ts +3 -2
  68. package/dist/environment/health-composition.js +19 -2
  69. package/dist/environment/init-application.d.ts +39 -14
  70. package/dist/environment/init-application.js +39 -10
  71. package/dist/environment/lifecycle-composition.d.ts +4 -2
  72. package/dist/environment/lifecycle-composition.js +84 -17
  73. package/dist/environment/lifecycle-stage.d.ts +8 -3
  74. package/dist/environment/lifecycle-stage.js +11 -1
  75. package/dist/environment/logs.d.ts +1 -1
  76. package/dist/environment/map-limit.d.ts +2 -0
  77. package/dist/environment/map-limit.js +13 -0
  78. package/dist/environment/mcp-observation.d.ts +9 -0
  79. package/dist/environment/mcp-observation.js +46 -0
  80. package/dist/environment/observations.d.ts +1 -1
  81. package/dist/environment/observations.js +2 -0
  82. package/dist/environment/operator-application.d.ts +35 -6
  83. package/dist/environment/operator-application.js +42 -0
  84. package/dist/environment/presence.d.ts +4 -0
  85. package/dist/environment/presence.js +26 -0
  86. package/dist/environment/read-composition.d.ts +8 -3
  87. package/dist/environment/read-composition.js +53 -8
  88. package/dist/environment/readiness-composition.d.ts +20 -1
  89. package/dist/environment/readiness-composition.js +23 -2
  90. package/dist/environment/reclaim.d.ts +56 -19
  91. package/dist/environment/reclaim.js +147 -42
  92. package/dist/environment/runtime.d.ts +64 -18
  93. package/dist/environment/runtime.js +73 -3
  94. package/dist/environment/served-checkout-grants.d.ts +53 -0
  95. package/dist/environment/served-checkout-grants.js +182 -0
  96. package/dist/environment/sibling-checkouts.d.ts +119 -0
  97. package/dist/environment/sibling-checkouts.js +254 -0
  98. package/dist/environment/sibling-observation.d.ts +27 -0
  99. package/dist/environment/sibling-observation.js +45 -0
  100. package/dist/environment/sibling-pull.d.ts +23 -0
  101. package/dist/environment/sibling-pull.js +95 -0
  102. package/dist/environment/stack-inputs.d.ts +18 -0
  103. package/dist/environment/stack-inputs.js +210 -0
  104. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  105. package/dist/environment/stack-inventory-prune.js +129 -0
  106. package/dist/environment/stack-inventory-report.d.ts +44 -0
  107. package/dist/environment/stack-inventory-report.js +78 -0
  108. package/dist/environment/stack-inventory.d.ts +94 -0
  109. package/dist/environment/stack-inventory.js +418 -0
  110. package/dist/environment/state.d.ts +48 -9
  111. package/dist/environment/state.js +142 -2
  112. package/dist/environment/tool-registration.js +3 -7
  113. package/dist/environment-exec/execute.d.ts +6 -3
  114. package/dist/environment-exec/execute.js +4 -0
  115. package/dist/fix-catalogue.d.ts +1 -1
  116. package/dist/generated-environment/adopt.js +4 -8
  117. package/dist/guards/answer-confirmation.d.ts +41 -0
  118. package/dist/guards/answer-confirmation.js +109 -0
  119. package/dist/guards/evaluator.d.ts +20 -3
  120. package/dist/guards/evaluator.js +39 -7
  121. package/dist/guards/local-identity.d.ts +6 -2
  122. package/dist/guards/local-identity.js +10 -4
  123. package/dist/guards/policy-change.d.ts +43 -0
  124. package/dist/guards/policy-change.js +159 -0
  125. package/dist/guards/policy-pin.d.ts +1 -1
  126. package/dist/guards/policy-pin.js +14 -12
  127. package/dist/init/command.d.ts +4 -3
  128. package/dist/init/command.js +36 -16
  129. package/dist/init/derive.d.ts +8 -6
  130. package/dist/init/derive.js +140 -36
  131. package/dist/init/draft.d.ts +13 -11
  132. package/dist/init/draft.js +54 -31
  133. package/dist/init/render.d.ts +18 -6
  134. package/dist/init/render.js +31 -9
  135. package/dist/repository-path.d.ts +9 -0
  136. package/dist/repository-path.js +16 -0
  137. package/dist/schema.d.ts +42 -75
  138. package/dist/schema.js +62 -11
  139. package/dist/surfaces/harness-registry.d.ts +80 -0
  140. package/dist/surfaces/harness-registry.js +29 -0
  141. package/dist/surfaces/mcp.d.ts +67 -17
  142. package/dist/surfaces/mcp.js +194 -31
  143. package/dist/surfaces/projection.d.ts +7 -7
  144. package/dist/surfaces/projection.js +41 -6
  145. package/dist/surfaces/render.d.ts +5 -44
  146. package/dist/surfaces/render.js +5 -2
  147. package/dist/team-metadata.js +2 -1
  148. package/dist/testing/test-protection.d.ts +34 -0
  149. package/dist/testing/test-protection.js +137 -0
  150. package/dist/testing/weakening.d.ts +15 -0
  151. package/dist/testing/weakening.js +30 -0
  152. package/dist/work-orders/application-closure.d.ts +7 -0
  153. package/dist/work-orders/application-closure.js +76 -0
  154. package/dist/work-orders/application-context.d.ts +162 -0
  155. package/dist/work-orders/application-context.js +137 -0
  156. package/dist/work-orders/application-decisions.d.ts +27 -0
  157. package/dist/work-orders/application-decisions.js +121 -0
  158. package/dist/work-orders/application-grading.d.ts +11 -0
  159. package/dist/work-orders/application-grading.js +212 -0
  160. package/dist/work-orders/application.d.ts +4 -97
  161. package/dist/work-orders/application.js +94 -254
  162. package/dist/work-orders/composition.d.ts +35 -58
  163. package/dist/work-orders/composition.js +216 -24
  164. package/dist/work-orders/contract.d.ts +22 -6
  165. package/dist/work-orders/contract.js +34 -16
  166. package/dist/work-orders/decisions.d.ts +23 -0
  167. package/dist/work-orders/decisions.js +55 -0
  168. package/dist/work-orders/store.d.ts +54 -5
  169. package/dist/work-orders/store.js +118 -24
  170. package/docs/README.md +6 -3
  171. package/docs/capability-matrix.md +17 -9
  172. package/docs/cli-reference.md +209 -1
  173. package/docs/command-list.md +12 -1
  174. package/docs/concepts.md +6 -4
  175. package/docs/configuration-essentials.md +7 -2
  176. package/docs/configuration-reference.md +18 -3
  177. package/docs/docs-check.md +76 -0
  178. package/docs/environment.md +194 -6
  179. package/docs/faq.md +5 -3
  180. package/docs/getting-started.md +109 -25
  181. package/docs/harness-surfaces.md +69 -2
  182. package/docs/safety-and-autonomy.md +56 -12
  183. package/docs/troubleshooting.md +29 -8
  184. package/docs/tutorial.md +3 -2
  185. package/docs/work-and-browser.md +180 -14
  186. package/libexec/browser-operation.cjs +86 -2
  187. package/package.json +2 -2
  188. package/surfaces/skills/cockpit-browser.skill.md +7 -2
  189. package/surfaces/skills/cockpit-explain.skill.md +1 -1
  190. package/surfaces/skills/cockpit-fix.skill.md +1 -1
  191. package/surfaces/skills/cockpit-setup.skill.md +3 -3
  192. package/surfaces/skills/cockpit-work.skill.md +1 -1
  193. package/surfaces/skills/procedures/explain.md +9 -4
  194. package/surfaces/skills/procedures/fix.md +25 -11
  195. package/surfaces/skills/procedures/setup.md +113 -40
  196. package/surfaces/skills/procedures/work.md +45 -16
@@ -32,6 +32,10 @@ npm install --save-dev --save-exact @b00dea/cockpit
32
32
  npx --no cockpit init . --target claudecode
33
33
  ```
34
34
 
35
+ With pnpm or yarn, install with `pnpm add -D -E @b00dea/cockpit` or `yarn add -D -E @b00dea/cockpit`
36
+ and run `pnpm exec cockpit` or `yarn cockpit` wherever this guide runs cockpit through `npx --no`. `init`
37
+ detects `pnpm-lock.yaml` or `yarn.lock` and writes the matching commands into what it proposes.
38
+
35
39
  Replace `claudecode` with `codexcli` or `opencode` for those tools, or list several, yours first
36
40
  (`--target claudecode,opencode`). `init` records them as `[surfaces] tools` and installs the
37
41
  `cockpit-setup` skill for each listed tool. The default tools are `claudecode`, `codexcli` and
@@ -90,25 +94,40 @@ same procedure.
90
94
  A clean-looking configuration with invented values in it is the failure cockpit exists to prevent.
91
95
  The agent follows these rules for the whole setup:
92
96
 
93
- 1. **Ask the human first, and wait for a yes,** before writing into a product repository (a
94
- Dockerfile, Compose file, `.env.schema` or anything else in a sibling checkout), creating a Git
95
- remote or hosted repository, running a destructive command (`down`, `reset`, `sync --interactive`
96
- removals, deleting files), running `cockpit sync --adopt`, or accepting an `init` digest. A person
97
- approves or runs staging and the commit of the cockpit: the agent hands them the command.
97
+ 1. **Ask the human first, and wait for a yes,** before creating a Git remote or hosted repository,
98
+ running a destructive command (`reset`, `sync --interactive` removals, deleting files), running
99
+ `cockpit sync --adopt`, accepting an `init` digest, or changing a product repository in any other
100
+ way than the next sentences allow (adopting a test framework or a dependency, for example). A
101
+ person approves or runs staging and the commit of the cockpit: the agent hands them the command.
102
+ A Dockerfile, Compose file or `.env.schema` the product needs goes on a new branch in that
103
+ sibling's checkout, started with no uncommitted changes, and the hand-back shows its diff against
104
+ the branch it started from. Nothing is pushed, so the person reviews it before it reaches anyone
105
+ else.
98
106
  2. **Show the exact preview before asking:** the `sync --dry-run` listing, the `init` preview with its
99
107
  `previewSha256`, the answers file or the defaults digest. Consent binds to those bytes, and a
100
108
  digest from anything but the latest pending run is refused.
101
109
  3. **Never invent a configuration value.** A port, image, command, health check, purpose or name
102
110
  comes from a file the agent read or from the human; otherwise it leaves a named gap and asks.
103
- 4. **Inferred values need confirmation.** `init` proposes values with `provenance.kind = "inferred"`
104
- and the file each came from; they become answers only on the human's word.
111
+ 4. **Only what init read exactly is the agent's to accept.** `init` labels each proposal with
112
+ `provenance.kind`: `derived` (the complete answer, read from the named file and line),
113
+ `measured` (observed on this machine) or `inferred` (a hint or guess). A field whose
114
+ `confirmedBy` is `"agent"` may be answered with `"derived"` (below); `init` checks the value
115
+ against what it read, and bring-up proves it. Every other field, and every security boundary
116
+ (the `guards` hosts, protected branches, guard and MCP allow-lists), becomes an answer only on the
117
+ human's word.
105
118
  5. **Ask for every non-local host.** The `guards` answer lists the database and live vendor API hosts
106
119
  of production, staging and shared environments. Variable names come from env templates, never
107
120
  values; each host comes from the human.
108
121
  6. **Defaults never reach into a product repository.** "Use sensible defaults" covers `init` answers
109
- only. A pointer in a sibling's **AGENTS.md**, a Compose file or a `package.json` still needs an
110
- explicit yes to that exact change. Every answer the agent chose itself is marked `"agent-default"`
111
- (below), and its report ends with each default, its field and its value.
122
+ only. A pointer in a sibling's **AGENTS.md** or a change to its `package.json` still needs an
123
+ explicit yes to that exact change, and a product file goes on a branch as rule 1 says. Every
124
+ answer the agent chose itself is marked `"agent-default"` (below), and its report ends with each
125
+ default, its field and its value.
126
+ 7. **Confirm a default only by observing it.** The agent clears an `unconfirmed` entry only with
127
+ `cockpit confirm`, which runs the observation that verifies that answer on this machine and
128
+ records it. The trust decisions (`guards`, `testing`, `environment.install_policies`, `sibling`,
129
+ `environment.posture`) and the answers no observation settles (`cockpit.name`, `estate.runnable`,
130
+ `environment.test_data`) stay a person's (below).
112
131
 
113
132
  These rules are instructions, not enforcement. The guard cockpit installs later blocks some commands;
114
133
  see [Safety and autonomy](safety-and-autonomy.md).
@@ -135,9 +154,48 @@ guards = "agent-default"
135
154
  ```
136
155
 
137
156
  Each such field is written under `unconfirmed` at the top of `cockpit.toml`, listed in
138
- `result.unconfirmed`, and reported by `doctor` as `DECLARATION_UNCONFIRMED` until a person confirms
139
- the value, deletes its `unconfirmed` entry and runs `cockpit sync`. An `environment.machine` floor
140
- the file does not attribute is recorded as unattributed, because cockpit never proposes one.
157
+ `result.unconfirmed`, and reported by `doctor` as `DECLARATION_UNCONFIRMED` until it is confirmed. An
158
+ `environment.machine` floor the file does not attribute is recorded as unattributed, because no file
159
+ records its free disk or cold start time.
160
+
161
+ Three answers are facts about this machine and the running product, so an agent confirms them by
162
+ observation, one at a time:
163
+
164
+ ```sh
165
+ ./node_modules/.bin/cockpit confirm ports.bases --config cockpit.toml --json
166
+ ```
167
+
168
+ | Answer | The observation that confirms it |
169
+ |---|---|
170
+ | `ports.bases` | `preflight` passes with the stack up and its port family leased on the declared bases |
171
+ | `environment.compose_files` | `check` passes, which parses the Compose files and resolves the stack's topology |
172
+ | `environment.machine` | the runtime measured on this machine meets the declared memory, CPU and free-disk floor |
173
+
174
+ When the observation passes, `confirm` removes that one entry from `unconfirmed` (comments and every
175
+ other byte stay) and records what ran, when, its result and an evidence digest in
176
+ `.cockpit/local/answer-confirmations`. That file is machine-local and ignored, because the evidence is
177
+ about this machine, and the guard denies agent writes to it. The guard treats the removed entry as
178
+ non-widening only while that record matches the configuration on disk, so the agent's own sync
179
+ applies it where `[autonomy] sync = "agent"`; any other edit, or a clone without the record, leaves
180
+ it to a person. A failed observation is refused with `CONFIRM_OBSERVATION_FAILED` beside the
181
+ diagnostics that explain it.
182
+
183
+ Every other answer is refused with `CONFIRM_PERSON_REQUIRED`: the trust decisions, and
184
+ `cockpit.name`, `estate.runnable` and `environment.test_data`, which state what the owner wants rather
185
+ than anything a machine can show. A person confirms the value with whoever owns the product, corrects
186
+ it if it is wrong, deletes its `unconfirmed` entry and runs `cockpit sync` in a terminal.
187
+
188
+ A field whose `interview.outstanding` entry says `confirmedBy = "agent"` can instead be marked
189
+ `"derived"`, with or without a value: `init` answers it with the value it read, refuses
190
+ (`DOCUMENT_CONFIGURATION_ANSWER_NOT_DERIVED`) a value that differs or a field it read no complete value
191
+ for, and writes a `# Derived:` comment naming the source instead of an `unconfirmed` entry:
192
+
193
+ ```toml
194
+ [provenance]
195
+ "cockpit.name" = "derived"
196
+ "ports.bases" = "derived"
197
+ "environment.compose_files" = "derived"
198
+ ```
141
199
 
142
200
  ### Walk through readiness
143
201
 
@@ -147,9 +205,18 @@ has an `id`, a `level` (a `required` gap is refused by the command that needs it
147
205
  warns), `needs` (why cockpit needs it) and `findings` (each diagnostic with its message and fix).
148
206
  `noFinding` lists the prerequisites no check reported; a check that could not run reports nothing, so
149
207
  it is not proof. Take the gaps one at a time, required first: what is missing, why, and the exact change
150
- you propose. Judgement calls (which test runner to adopt, whether the existing tests prove anything)
151
- are the human's. The cockpit directory must be a Git working tree, committed and clean, before
152
- `preflight` passes; a new one reports `GIT_STATE_UNHEALTHY` until a person stages and commits it.
208
+ you propose.
209
+
210
+ Whether the existing tests prove anything is the agent's judgement, made on evidence. For each
211
+ proposed gate, name the behaviours its tests exercise, then break one of them on purpose, run the
212
+ gate, watch it go red, and restore the file so the checkout is as it was found; that edit needs no
213
+ yes, because it is undone before the step ends. Write the behaviours into the gate's `changes` text
214
+ and report the judgement, with the red run's output, in the hand-back. A gate that stays green on the
215
+ break proves nothing about that behaviour, and the report says so. Adopting a new test framework in a
216
+ product repository stays the human's call.
217
+
218
+ The cockpit directory must be a Git working tree, committed and clean, before `preflight` passes; a
219
+ new one reports `GIT_STATE_UNHEALTHY` until a person stages and commits it.
153
220
 
154
221
  ## The init interview
155
222
 
@@ -178,14 +245,22 @@ depends on what it finds:
178
245
  `mount = "none"`).
179
246
  - **A declared runtime** adds questions about the machine floor and posture the product needs.
180
247
 
181
- Proposals come only from exact sources: a Dockerfile's `EXPOSE`, `CMD` and `ENTRYPOINT`, a
248
+ Each outstanding field lists its `proposals`, and `confirmedBy` says who settles it: `"agent"` when a
249
+ `derived` proposal answers the whole field (today `cockpit.name`, `ports.bases` and
250
+ `environment.compose_files`, when the files leave no gap), `"person"` otherwise. A source `init`
251
+ could not read, or two that disagree, such as a host port with no default or a memory limit set twice
252
+ with different values, leaves the field to the person. Proposals come only from exact sources: a Dockerfile's `EXPOSE`, `CMD` and `ENTRYPOINT`, a
182
253
  `package.json` `start`, `serve` or `dev` script, the `node` toolchain a lockfile names, each
183
254
  repository's `origin` remote (a URL carrying credentials is skipped, never shown), variable names
184
255
  (never values) in env templates such as `.env.example`, an existing Compose file, the `services:` of
185
256
  GitHub workflows, and test entry points such as a Makefile `test` target, a `package.json` test script
186
257
  or `go.mod`. A name that suggests a database or vendor endpoint, such as `DATABASE_URL`, becomes a
187
- `guards` question about its host. Sources `init` could not use are listed in
188
- `interview.inferenceSkipped`.
258
+ `guards` question about its host. For the machine floor, `init` sums the memory and CPU limits of the
259
+ services the Compose file starts by default (`deploy.resources.limits` or `mem_limit` and `cpus`,
260
+ times their replicas), and where no limit records one it measures this machine's container runtime
261
+ (`measured`, which refuses a smaller machine until someone lowers it). Free disk and the cold start
262
+ time have no source and are never proposed. Sources `init` could not use, including a runtime it
263
+ could not measure, are listed in `interview.inferenceSkipped`.
189
264
 
190
265
  ### Without a terminal
191
266
 
@@ -193,12 +268,15 @@ An agent or CI job has no terminal, so `--interactive` refuses with `INIT_TERMIN
193
268
  consent and answers as arguments instead; there is no blanket `--yes`.
194
269
 
195
270
  1. Run the `--json` preview. It returns the `previewSha256` and, under `interview.outstanding`, every
196
- field the interview will still ask, in order, each with a `why` and the values `init` could propose.
271
+ field the interview will still ask, in order, each with a `why`, its `proposals` and `confirmedBy`.
197
272
  2. Write the answers as TOML keyed by field, and show them to the human with the digest.
198
273
  3. Rerun with `--accept-preview <previewSha256>` and `--answers <file>`. The run stops at the first
199
274
  unanswered field, or at `configuration.defaults`, where `result.question.document` is the review
200
- draft (an array of lines) and `result.question.expectedSha256` covers its values. The human reads
201
- the draft before accepting that digest.
275
+ draft (an array of lines). Its provenance comments split the surfaced defaults in two: schema
276
+ defaults and the cockpit's own layout, which the agent accepts, and the security boundaries
277
+ (protected branches, guard allow and deny lists, approved MCP servers, resettable volumes,
278
+ generated host paths), which a person confirms. `result.question.expectedSha256` covers only the
279
+ second group, and the human reads it before accepting that digest.
202
280
  4. Rerun with `--accept-defaults <expectedSha256>` as well. A digest that is not the pending one is
203
281
  refused (`INIT_PREVIEW_NOT_ACCEPTED`, `INIT_DEFAULTS_NOT_ACCEPTED`).
204
282
 
@@ -239,11 +317,11 @@ vendor_endpoint_hosts = []
239
317
  | `environment.install_policies` | the build and test commands per toolchain and lockfile; `[]` for none |
240
318
  | `environment.test_data` | exactly the boolean keys `seeded_data` and `signed_in_users`; `false` states it is not needed |
241
319
  | `environment.compose_files` | the Compose files that run the product, relative to the cockpit root |
242
- | `environment.machine` | the runtime floor: `runtime_memory_mb`, `runtime_cpus`, `free_disk_mb`, `cold_start_seconds`; never proposed |
320
+ | `environment.machine` | the runtime floor: `runtime_memory_mb`, `runtime_cpus`, `free_disk_mb`, `cold_start_seconds`; memory and CPUs are proposed, free disk and cold start never |
243
321
  | `environment.posture` | `addressing`, `authentication` and `posture_requirements` ([Declare environment postures](environment.md#declare-environment-postures)) |
244
322
  | `testing` | the product's `[testing]` stages and gates; `"none"` records that it has no tests yet, so no work can be graded PASS and `doctor` warns (`TESTING_GATE_UNDECLARED`) |
245
323
  | `guards` | `hosted_db_hosts` and `vendor_endpoint_hosts` of every non-local environment; `[]` for both records that none exists |
246
- | `provenance` | optional; `"human"` (the default) or `"agent-default"` per field |
324
+ | `provenance` | optional; `"human"` (the default), `"agent-default"` or `"derived"` per field. An `"agent-default"` answer stays under `unconfirmed` until `cockpit confirm` observes it or a person confirms it ([Mark the answers you chose](#mark-the-answers-you-chose)) |
247
325
 
248
326
  A key that names no field is refused. Recorded answers are kept, so the same file can be passed on every
249
327
  run; a different value for a recorded field is refused (`INIT_ANSWER_CONFLICT`) before anything is
@@ -277,7 +355,7 @@ compose_files = ["compose.yml"]
277
355
  `[ports.bases]` variable, and a service without a healthcheck warns. The rules are in
278
356
  [Environment lifecycle](environment.md#environment-modes); a working layout is
279
357
  [examples/compose-first](https://github.com/b0dea/cockpit/blob/main/examples/compose-first/README.md). Changing a product's Compose file is a
280
- product change: ask first.
358
+ product change: an agent makes it on a branch, as the [safety contract](#safety-contract) says.
281
359
 
282
360
  **Data.** `seed` and `resettable_volumes` fill and wipe the stack's data
283
361
  ([Demo data, test users and roles](environment.md#demo-data-test-users-and-roles)). `cockpit reset` previews the change and needs a person
@@ -557,6 +635,12 @@ group stops, `session` calls `sleep`, which stops the stack only if no other che
557
635
  it. Interrupting `session` stops only the process group it started, and a `session` inside another
558
636
  is refused. Use plain `up` only when the stack should outlive one command.
559
637
 
638
+ While it runs, `ps` shows each service, and `restart`, `rebuild` and `exec` act on one of them by its
639
+ `--service` name; `up --profile <name>` adds a Compose profile. To work on a sibling in a worktree,
640
+ `use <slug> <checkout>` points this cockpit checkout's stack at it, `pull` fast-forwards every
641
+ checkout, and `stacks prune` removes the stack a deleted worktree left behind. All of them are in
642
+ [Environment lifecycle](environment.md#serve-a-sibling-from-a-worktree).
643
+
560
644
  To remove the stack and its containers, run `down`. It reads the active Docker context once,
561
645
  refuses one that is not local, and deletes only from that daemon, so a deletion never lands on a
562
646
  remote daemon or on a context switched mid-command
@@ -171,8 +171,9 @@ refused even the `test-run` its own skill asks for. The generated `.claude/setti
171
171
  allows, by prefix, the cockpit commands the shipped skills tell an agent to run and the guard
172
172
  admits (see [Cockpit commands an agent can run](safety-and-autonomy.md#cockpit-commands-an-agent-can-run)),
173
173
  spelled both as `npx --no cockpit <command>` and as `./node_modules/.bin/cockpit <command>`.
174
- Person-only commands such as `sync`, `down` and `work accept` keep prompting. The guard hook still
175
- runs first, and its ask or deny wins over a rule. Claude Code ignores a project's allow rules
174
+ Commands that are always a person's, such as `init` and `sibling add`, have no rule and keep
175
+ prompting. A command the policy decides, such as `sync` or `work accept`, has a rule: the guard hook
176
+ still runs first, and its ask or deny wins over a rule. Claude Code ignores a project's allow rules
176
177
  until a person has trusted the workspace by starting Claude Code there once. Measured with Claude
177
178
  Code 2.1.316 on 2026-11-02 beside a command with no rule.
178
179
 
@@ -248,6 +249,52 @@ environment and pass `PATH`, the same non-secret variables product test stages r
248
249
  `USER`, `LOGNAME`, `TMPDIR`, `LANG`, `LC_ALL`, `LC_CTYPE`, `TZ`) and the declared environment
249
250
  names.
250
251
 
252
+ ### Remote servers
253
+
254
+ A remote (streamable HTTP) server is declared in the same file, in Claude Code's spelling:
255
+
256
+ ```json
257
+ { "mcpServers": {
258
+ "tracker": { "type": "http", "url": "https://mcp.example.test/mcp",
259
+ "headers": { "Authorization": "Bearer ${TRACKER_TOKEN}" } },
260
+ "docs": { "type": "http", "url": "https://docs.example.test/mcp", "headersHelper": "bin/mcp-headers docs" }
261
+ } }
262
+ ```
263
+
264
+ The URL must use `https`, or `http` only to a loopback host, and may carry no user information,
265
+ credential-named query parameter or `${...}` reference. Each header value is exactly `${NAME}`, or
266
+ `Bearer ${NAME}` in `Authorization`; a literal is refused, so no credential enters a tracked file.
267
+ `headersHelper` is a command and plain arguments, relative to the sibling, that prints a JSON object
268
+ of headers when the tool connects; it runs without a shell, so quoting and expansion belong in the
269
+ script. SSE servers are refused because Codex cannot connect to them. The approval digest covers the
270
+ URL, the header references and the helper's command, arguments and directory, so changing any of
271
+ them needs a new approval. Like a local server's command, the helper script's content is not in the
272
+ digest.
273
+
274
+ | Tool | Header references | Headers helper |
275
+ | --- | --- | --- |
276
+ | Claude Code | `headers` in `.mcp.json`, expanded from its environment | `headersHelper` |
277
+ | Codex | `bearer_token_env_var` and `env_http_headers` in `.codex/config.toml` | `http_headers_helper` |
278
+ | OpenCode | `{env:NAME}` in the `headers` of a `remote` server | none: the server is not written |
279
+ | Cursor, Grok Build | not written | not written |
280
+
281
+ A tool that cannot carry a server's credential gets nothing for that server, rather than a server
282
+ that starts and fails to authenticate; `sync` and `check` report `MCP_TRANSPORT_UNAVAILABLE` with the
283
+ tools it was left out of. The [capability matrix](capability-matrix.md) records the run behind each
284
+ cell. Claude Code runs a `headersHelper` from the project `.mcp.json` only after a person trusts the
285
+ workspace; until then it connects without those headers.
286
+
287
+ A header reference resolves from the environment the agent tool was launched in, so a tool started
288
+ from a desktop launcher may not see a variable set in a shell profile. A helper reads the credential
289
+ when the tool connects instead, wherever it keeps it. The helper runs through a generated wrapper,
290
+ `.cockpit/mcp/<id>.headers.sh`, started from the cockpit root like a local server's wrapper: it
291
+ changes to the sibling and passes the same inherited variables, without the header names.
292
+
293
+ `doctor` checks each approved server without starting it or using the network: a local server's
294
+ command and a remote server's helper must resolve (`MCP_COMMAND_UNRESOLVED`), and every environment
295
+ name it reads must be set where doctor runs (`MCP_ENVIRONMENT_UNSET`). It names variables, never
296
+ values.
297
+
251
298
  Browser transport is published only for harnesses that can use it. A harness with no applicable
252
299
  MCP server receives no MCP-only output; its instructions, skills and permissions still publish.
253
300
  Verify the result with `./node_modules/.bin/cockpit check --config cockpit.toml --json` after
@@ -294,6 +341,26 @@ project root, so the committed pattern is `{file:.cockpit/local/opencode-grant-<
294
341
  substitutes the absolute sibling directory that `sync` writes to that machine-local, ignored file,
295
342
  and refuses to start, naming the file, until `sync` has run on the machine.
296
343
 
344
+ A checkout `cockpit use` serves from outside the declared one needs a grant that names a path on one
345
+ machine, which no committed file can hold. Measured on 2026-11-04, each run writing into the granted
346
+ directory and into an ungranted control directory beside it:
347
+
348
+ - Claude Code 2.1.318, `claude -p --permission-mode acceptEdits`: a directory listed in
349
+ `.claude/settings.local.json` `permissions.additionalDirectories` was written and the control was
350
+ refused. A twin whose list was a string was reported as not loaded, and granted nothing. The run
351
+ was in an untrusted workspace, where Claude Code ignored the committed `.claude/settings.json`
352
+ list, so whether the two lists combine once a person trusts the workspace is unmeasured. `use`
353
+ writes the served checkout there.
354
+ - OpenCode 1.19.5, `opencode run`: the grant file holding the worktree path allowed the write, the
355
+ control was denied, and a pattern naming a missing grant file stopped OpenCode at start, naming the
356
+ file. `use` and `sync` write the served checkout there.
357
+ - Codex 0.166.0, `codex exec`: Codex reads no machine-local project file; its only per-project user
358
+ setting is the trust level. `--add-dir <dir>` at start made the worktree writable beside the
359
+ committed writable roots, and without it the same write was refused. `use` writes nothing for
360
+ Codex and prints that flag.
361
+
362
+ Cursor and Grok Build were not installed, so `use` writes nothing for them and says so.
363
+
297
364
  The `[cockpit].guide_cap` limit applies to always-loaded guidance, copied sibling guides included;
298
365
  going over it makes `sync`, `check` and `preflight` report `GUIDE_CAP_EXCEEDED` with the measured
299
366
  lines and the cap. The same code reports cockpit's region of **AGENTS.md** passing 32768 bytes,
@@ -28,7 +28,12 @@ A cockpit command is allowed only when it matches an exact available public comm
28
28
  application verifies the executable and working root; the command resolver checks the bounded
29
29
  complete argument vector, resolved configuration path, declared services, permitted option roles
30
30
  and inspected paths. Internal guard transport and public commands marked unavailable are excluded.
31
- Declared project build and test commands are allowed only as their exact argument vectors in their
31
+ The command after `session --` or `exec --` is judged as one more invocation, so neither admits
32
+ what the guard would refuse on its own. `exec` runs it in a product container whose files the
33
+ guard cannot see, so a read-only proof about host paths does not carry over: the name-based
34
+ denials (`.env*` and the rest) still apply to it, and it is admitted only by a `[guards] allow`
35
+ rule or as a declared build or test command; otherwise it asks, which Codex and OpenCode turn into
36
+ a denial. Declared project build and test commands are allowed only as their exact argument vectors in their
32
37
  own checkout. A wildcard over a command family never grants permission.
33
38
 
34
39
  ## Hard controls and prompts
@@ -69,7 +74,8 @@ truncating `dd` or `tee` writes, `ln` onto either file, a `chmod` mode that drop
69
74
  permission or is copied with `--reference`, a patch delete or move, a Write tool call with empty
70
75
  content, and a `>` redirect. Editing the configuration in place stays permitted, including bounded
71
76
  `dd conv=notrunc` and `tee` append forms under the normal write checks; the edit takes effect only
72
- after a human runs `sync`.
77
+ after `sync` runs, and only a person's sync applies an edit that
78
+ [widens what agents may do](#edits-the-guard-has-not-applied).
73
79
 
74
80
  Writing anywhere under a `.git` directory is a hard denial, because Git configuration chooses
75
81
  programs that read-only Git commands run. For the same reason, so is every `git config` form other
@@ -220,19 +226,24 @@ cockpit commands in
220
226
 
221
227
  | Command | Decision | Why |
222
228
  | --- | --- | --- |
223
- | `npx --no cockpit check --config cockpit.toml`, and likewise `doctor`, `preflight`, `logs --service <service>` and `sync --dry-run` | allowed | They read and report; nothing they do changes a file the guard relies on. |
229
+ | `npx --no cockpit check --config cockpit.toml`, and likewise `doctor`, `preflight`, `ps`, `logs --service <service>`, `stacks`, `stacks prune`, `docs check` and `sync --dry-run` | allowed | They read and report; nothing they do changes a file the guard relies on. |
224
230
  | `npx --no -- cockpit --help`, `npx --no cockpit up --help`, and the version | allowed | They print and exit before any command runs. |
225
- | `npx --no cockpit up --config cockpit.toml`, and likewise `sleep` and `down` | allowed | They act only on the stack cockpit declared and owns; `down` keeps data volumes, and deletes only from the local Docker daemon it fixes at the start of the run. |
231
+ | `npx --no cockpit up --config cockpit.toml`, and likewise `sleep`, `down`, `restart --service <service>` and `rebuild --service <service>` | allowed | They act only on the stack cockpit declared and owns; `down` keeps data volumes, and deletes only from the local Docker daemon it fixes at the start of the run. |
232
+ | `up --profile <profile>` | allowed for a profile in `environment.profiles` that mounts no sibling with `gate = "operator"`, otherwise a prompt | Another profile starts services no person declared for agents, and an operator-gated sibling touches live data or local hardware. |
233
+ | `npx --no cockpit exec --config cockpit.toml --service <service> -- <command>` | allowed when a `[guards] allow` rule or a declared build or test command admits `<command>`, otherwise a prompt | The command runs in a container whose files the guard cannot see, so it is never proven read-only, and the name-based denials still apply to it. |
234
+ | `npx --no cockpit pull --config cockpit.toml` | allowed | It only fetches and fast-forwards, and leaves a modified or diverged checkout as it was. |
235
+ | `npx --no cockpit use <slug> <checkout> --config cockpit.toml`, and `use <slug> --clear` | allowed for a checkout inside a directory the agent may already write, otherwise a prompt | The choice is recorded for this checkout of the cockpit only, so it never widens what the agent can reach. |
226
236
  | `npx --no cockpit runtime measure --config cockpit.toml --image <local-image>` | allowed | The probe runs a disposable stack under its own project, removes it, and records the result only in this machine's git-ignored state; `cockpit.toml` never changes. |
227
- | `npx --no cockpit session --config cockpit.toml -- <command>` | the decision for `<command>` | A session runs its command as the agent could run it directly, so it never admits more. |
237
+ | `npx --no cockpit session --config cockpit.toml -- <command>`, with a `--profile` that `up` admits | the decision for `<command>` | A session runs its command as the agent could run it directly, so it never admits more. |
228
238
  | `npx --no cockpit test-run --config cockpit.toml --gate <gate>`, with an optional `--mode` and a `--select <kind>:<value>` whose value does not start with `-` | allowed for a gate `cockpit.toml` declares | The gate runs only the stages a person declared in the reviewed configuration, the trust `up` already has. |
229
- | `npx --no cockpit work status --config cockpit.toml`, `work accept`, `work check`, `work note`, `work answer`, `work grade`, `work signoff`, `work recover` without `--apply`, `work verify` and `cockpit grade --criteria <file>`, each with its own options | allowed | They report, or take a step [`[work.gates]`](#who-decides) gives the agent; a step it gives a person waits for the digest typed after `--interactive`, which no agent invocation carries. Grading runs in a separate evaluator. |
230
- | `npx --no cockpit sync --config cockpit.toml`, and `upgrade`, whose sync child writes | allowed where `[autonomy] sync = "agent"`, otherwise a prompt | It regenerates the files the configuration already declares; the guard holds every call after `cockpit.toml` changes until a person syncs, so an agent's sync never publishes an edit of its own. |
239
+ | `npx --no cockpit work status --config cockpit.toml`, `work accept`, `work check`, `work note`, `work decide`, `work answer`, `work acknowledge`, `work grade`, `work signoff`, `work recover` (with or without `--apply`), `work verify` and `cockpit grade --criteria <file>`, each with its own options | allowed | They report, or take a step [`[work.gates]`](#who-decides) gives the agent; a step it gives a person waits for the digest typed after `--interactive`, which no agent invocation carries. Grading runs in a separate evaluator. `work recover --apply` removes a lock only once it has shown the holder gone on the lock's own host and among the containers cockpit labels. |
240
+ | `npx --no cockpit sync --config cockpit.toml`, and `upgrade`, whose sync child writes | allowed where `[autonomy] sync = "agent"`, otherwise a prompt | It regenerates the files the configuration declares; after `cockpit.toml` changes, the guard admits it only when the edit [does not widen](#edits-the-guard-has-not-applied) what agents may do, so an agent's sync never publishes a widening of its own. |
241
+ | `npx --no cockpit tool add <tool> --config cockpit.toml`, with an optional `--dry-run` | allowed | It edits only `[surfaces] tools`, and an added tool [does not widen](#edits-the-guard-has-not-applied) what agents may do, so the agent's sync applies it. |
231
242
  | `npx --no cockpit reset --config cockpit.toml --apply` | allowed where `[autonomy] reset = "agent"`, otherwise a prompt | It deletes stack data. |
232
- | `npx --no cockpit browser drive --config cockpit.toml`, and likewise `screenshot`, with an optional `--role` | allowed where `browser.request_preapproval` is set, otherwise a prompt | They sign in and capture the declared application, so they run unprompted only on the isolated, non-production data the configuration must declare before it can set preapproval. |
233
- | `work accept`, `work answer` and `work signoff` with `--interactive` | a prompt, which Codex and OpenCode turn into a denial | `--interactive` asks for a person at a terminal to type the digest of what it shows. |
234
- | `work recover --apply` | a prompt, which Codex and OpenCode turn into a denial | It deletes a lock once a process probe calls its holder dead, and that probe sees only this machine's processes: a holder in a container or on another host that shares the checkout looks dead to it. |
235
- | `sync --adopt`, `sync --interactive`, `reset --interactive`, `sibling add` and every other command | a prompt, which Codex and OpenCode turn into a denial | They rewrite `cockpit.toml` or a team's own settings, ask for a person, or delete data. |
243
+ | `npx --no cockpit stacks prune --config cockpit.toml --apply` | allowed; the command itself refuses a prune that deletes volumes unless `[autonomy] reset = "agent"` | It removes only stacks whose checkout is gone, which bringing the stack up again recreates, and their data only where a reset could delete it. |
244
+ | `npx --no cockpit browser drive --config cockpit.toml`, and likewise `screenshot`, with an optional `--role` | allowed where `browser.request_preapproval` is on, otherwise a prompt | They sign in and capture the declared application, so they run unprompted only on the isolated, non-production data the configuration declares. Preapproval is on by default exactly when `browser.data` is isolated, not restored and not reaching production, and off otherwise; an explicit `request_preapproval` wins, and `true` on a posture that does not qualify is refused. `drive` also accepts `--fail-on-findings`. |
245
+ | `work accept`, `work answer`, `work acknowledge` and `work signoff` with `--interactive` | a prompt, which Codex and OpenCode turn into a denial | `--interactive` asks for a person at a terminal to type the digest of what it shows. |
246
+ | `sync --adopt`, `sync --interactive`, `reset --interactive`, `stacks prune --interactive`, `sibling add`, `sibling remove`, `tool remove` and every other command | a prompt, which Codex and OpenCode turn into a denial | They widen what agents may do through `cockpit.toml`, rewrite a team's own settings, ask for a person, or delete data. |
236
247
 
237
248
  A file such a command names, like `--criteria`, is checked as a read:
238
249
  the command may quote it in a refusal. A declared instance, a file below `.secrets` or a `.env` or
@@ -339,7 +350,7 @@ templates too, which `cat` in a shell still reads.
339
350
  | --- | --- | --- |
340
351
  | `sync` | whether the guard lets an agent run a writing `sync`; under `ci` a writing sync runs only where `CI` is set | `agent` |
341
352
  | `stale_delete` | whether `sync` removes a file it generated before and no longer generates without a person typing the stale-removal digest | `agent` |
342
- | `reset` | whether `reset --apply` deletes stack data without a person; otherwise a person approves the preview with `--interactive` | `person` |
353
+ | `reset` | whether `reset --apply`, and a `stacks prune --apply` that deletes volumes, delete stack data without a person; otherwise a person approves the preview with `--interactive` | `person` |
343
354
  | `mcp_approve` | `person`: sync enables only the MCP servers whose digest a person recorded in `[mcp.approved]`, and `MCP_NOT_APPROVED` names the rest; `agent`: sync enables every declared server as declared now, which lets an agent start processes outside the guard, because a sibling's MCP source is a file the agent can write and each harness starts an enabled server outside every guard boundary | `person` |
344
355
 
345
356
  <!-- cockpit:ref path=packages/cockpit/src/guards/policy-pin.ts --> The guard pins the policy: an agent's edit, write or patch that would change `[work]`,
@@ -371,6 +382,39 @@ A local `person` gate is best-effort: cockpit cannot tell a person from a progra
371
382
  The only enforced human gate is a required CI job behind a protected environment whose reviewers must
372
383
  approve it, such as the [`work verify` job](work-and-browser.md#who-passes-each-gate).
373
384
 
385
+ ### Edits the guard has not applied
386
+
387
+ <!-- cockpit:ref path=packages/cockpit/src/guards/policy-change.ts --> Once `cockpit.toml` differs
388
+ from the configuration the guards were generated from, the guard loads both, the earlier from the
389
+ copy sync keeps in `.cockpit/local/guard-config`, and compares them with defaults applied. An edit that does
390
+ not widen what agents may do holds every tool call except `cockpit sync` and `cockpit check`, which
391
+ the agent may run under the rules above, and the hold says so (`GUARD_CONFIG_UNSYNCED`). Any other
392
+ edit holds every call, that sync included, until a person runs `sync` in a terminal, and the hold
393
+ names each field that widens.
394
+
395
+ An edit does not widen only when every difference is one of these:
396
+
397
+ - descriptive or host-only fields: a sibling's `purpose`, `host_reason` and `rebuild_inputs`,
398
+ `cockpit.guide_cap`, `[ports]`, `environment.entrypoint_derivatives`, `environment.machine` and
399
+ `docs.living`;
400
+ - added agent tools, `unconfirmed` answers and sibling operator gates;
401
+ - removed Compose files, Compose profiles and resettable volumes, and an install policy that admits no
402
+ command it did not.
403
+
404
+ Every other difference widens. That includes any change, in either direction, to `[guards]`,
405
+ `[work]`, `[autonomy]`, `[mcp]`, `[testing]` (whose stages `test-run` runs unprompted) and `[browser]`;
406
+ a new sibling, wherever its directory is; a sibling's changed `dir`, `guides` (sync copies each guide
407
+ into the root `AGENTS.md`), environment files or derived MCP sources; and every command field, such
408
+ as `environment.seed`. A field the classification has no rule for widens too, so a field added to
409
+ the schema is a person's decision until a rule says otherwise. A missing or mismatched
410
+ copy of the generated configuration cannot show an edit safe, so it holds as a widening edit does.
411
+ `check` reports the same classification: `GUARD_CONFIG_UNSYNCED` as a warning, `GUARD_CONFIG_WIDENED`
412
+ as an error naming the fields.
413
+
414
+ Whoever runs it, sync refuses a guide that is, or links to, a `.env` or `.env.*` file, a file below a
415
+ `.secrets` directory or a declared environment instance (`PROJECTION_GUIDE_SECRET`), so a guide never
416
+ copies what the guard keeps from agents into the root `AGENTS.md`.
417
+
374
418
  ## Native adapter behavior
375
419
 
376
420
  The [transport and adapter owner](https://github.com/b0dea/cockpit/blob/main/packages/cockpit/src/guards/harness-adapter.ts) translates a
@@ -121,7 +121,8 @@ the harness enforces anything.
121
121
  ### MCP and restrictions
122
122
 
123
123
  For `MCP_INPUT_INVALID` or `MCP_DECLARATION_INVALID`, correct the declaration, digest,
124
- same-name environment reference, or confined working directory named by the message.
124
+ same-name environment reference, confined working directory, or remote server URL, header
125
+ reference or `headersHelper` named by the message.
125
126
  `MCP_COMMAND_CONFLICT` means two sources give one server name different commands; make them agree or give them distinct names. `MCP_NOT_APPROVED` leaves a declared
126
127
  server disabled because `[mcp.approved]` has no entry for it, or its declaration changed since it
127
128
  was approved; a person, only after reviewing its command, arguments, working directory, environment
@@ -129,6 +130,9 @@ names and sources, sets its name to the sha256 in the message under `[mcp.approv
129
130
  While `[autonomy] mcp_approve` is `person`, the default, the guard refuses an agent's edit of `[mcp]`. `MCP_APPROVAL_UNKNOWN` means the allowlist names no
130
131
  declaration. `MCP_EXECUTABLE_UNAVAILABLE` comes from the generated wrapper when its declared
131
132
  executable is absent or not executable; restore that executable rather than changing the wrapper.
133
+ `MCP_TRANSPORT_UNAVAILABLE` names a remote server left out of the tools that cannot carry its
134
+ credential, such as OpenCode for a server with a `headersHelper`; see
135
+ [Remote servers](harness-surfaces.md#remote-servers) for what each tool can carry.
132
136
 
133
137
  For `RESTRICTION_INPUT_INVALID`, `RESTRICTION_ADAPTER_INVALID`, `RESTRICTION_EVENT_UNADAPTED`,
134
138
  `RESTRICTION_TEMPLATE_INVALID` or `RESTRICTION_OUTPUT_LIMIT`, correct the named guard policy,
@@ -202,7 +206,17 @@ progress per checkout. A state diagnostic's target names the exact directory.
202
206
 
203
207
  These records are how cockpit knows which containers and ports a stack owns. Do not edit them by
204
208
  hand, and do not delete them while the stack runs: its containers would keep running with nothing
205
- that knows about them. To clear one cockpit's state:
209
+ that knows about them.
210
+
211
+ A stack whose worktree was deleted, and state folders an earlier cockpit build left, are removed by
212
+ `stacks prune`, which previews by default and removes only what no checkout can reach; see
213
+ [List and prune stacks across the machine](environment.md#list-and-prune-stacks-across-the-machine).
214
+ `doctor` warns with `STACKS_ORPHANED` when it finds such a stack, and with `STATE_RECORD_MALFORMED`
215
+ for another cockpit's state folder this version cannot read; `up` and `sleep` ignore that folder's
216
+ port leases.
217
+
218
+ `stacks prune` keeps a stack whose checkout still exists. To clear the state of a checkout you still
219
+ have:
206
220
 
207
221
  1. Stop the stack and release its claims from the cockpit directory:
208
222
  `./node_modules/.bin/cockpit down --config cockpit.toml`.
@@ -212,10 +226,7 @@ that knows about them. To clear one cockpit's state:
212
226
  `rm -rf "$HOME/Library/Application Support/cockpit/state/v1/cockpits/cockpit_duo_<hash>"`.
213
227
 
214
228
  When this cockpit's own state cannot be read, runtime commands refuse with
215
- `STATE_RECORD_MALFORMED` or `STATE_FORMAT_UNSUPPORTED` until you clear it. Unreadable state of
216
- another cockpit, such as a directory left by an earlier cockpit build, does not stop you: `up` and
217
- `sleep` ignore its port leases, and only `doctor` warns with `STATE_RECORD_MALFORMED`, naming that
218
- directory and the `rm -rf` command. Remove it with the same steps once no cockpit uses it.
229
+ `STATE_RECORD_MALFORMED` or `STATE_FORMAT_UNSUPPORTED` until you clear it this way.
219
230
 
220
231
  <!-- cockpit:diagnostic-index:begin -->
221
232
  ## Codes with a fixed remedy
@@ -226,6 +237,7 @@ Every diagnostic message names what failed and how to fix it. These codes always
226
237
  | --- | --- |
227
238
  | `AUTONOMY_CI_ONLY` | push the change and let the CI job run the command, or have a person change the field in cockpit.toml and accept the change at sync |
228
239
  | `AUTONOMY_PERSON_REQUIRED` | a person runs the same command with --interactive in a terminal outside CI, or a person changes the field in cockpit.toml and accepts the change at sync |
240
+ | `BROWSER_ROUTE_FINDINGS` | this run asked for a clean route (--fail-on-findings). Read result.receipt.surfaces[].diagnostics, including excerpt.messages where present: fix the product for each console error, uncaught page error and failed request that matters, then rerun npx --no cockpit browser drive --config <your cockpit.toml> --route <name> --fail-on-findings; third-party traffic never counts |
229
241
  | `CLAIM_CO_TENANT` | [see above](#common-refusal-guidance) |
230
242
  | `COCKPIT_LAUNCHER_UNPROVEN` | run cockpit as ./node_modules/.bin/cockpit <command> --config <your cockpit.toml> from the cockpit root, or remove what stops npx from proving it: a .npmrc in the cockpit root, a bin or directories.bin in its package.json, or a node_modules/.bin/cockpit that npm install did not link to the installed package |
231
243
  | `COCKPIT_REVISION_UNSTAMPED` | install a released @b00dea/cockpit when the resolved data must name the exact cockpit source commit |
@@ -233,7 +245,13 @@ Every diagnostic message names what failed and how to fix it. These codes always
233
245
  | `COMPOSE_FILE_UNREADABLE` | make environment.compose_files name a readable Compose file inside the cockpit root, then run npx --no cockpit check --config <your cockpit.toml> |
234
246
  | `CONFIG_ENCODING_INVALID` | save the configuration as UTF-8, then retry |
235
247
  | `DATA_SEED_UNAVAILABLE` | make sure the Docker daemon this stack runs on answers docker volume ls, then rerun npx --no cockpit up --config <your cockpit.toml> |
236
- | `DECLARATION_UNCONFIRMED` | confirm each value with whoever owns the product and correct it in cockpit.toml if it is wrong, delete each confirmed entry from unconfirmed, then run npx --no cockpit sync --config <your cockpit.toml> |
248
+ | `DECISION_INDEX_ENTRY_MISSING` | correct the link to the record's current file name, or remove the entry if the record was deleted on purpose |
249
+ | `DECISION_LINK_UNRESOLVED` | correct the number or link to name an existing record, or restore the record it names |
250
+ | `DECISION_NUMBER_REUSED` | renumber the later record to the next free number, then update the index and every supersedes line that cites it |
251
+ | `DECISION_UNINDEXED` | add a line linking the record to the decision index |
252
+ | `DOCS_NOT_DECLARED` | add a [docs] table to cockpit.toml: living = ["README.md", "docs/**/*.md"] lists the documents that must stay true, and the optional decisions = "docs/adr" and decisions_index = "docs/adr/README.md" name the decision records and their index; then rerun npx --no cockpit docs check --config <your cockpit.toml> |
253
+ | `DOCS_REFERENCE_REMOVED` | cite the path that replaced it (a rename is named in the message), or rewrite or delete the sentence if what it described is gone; then rerun npx --no cockpit docs check --config <your cockpit.toml> |
254
+ | `DOCS_TARGET_REMOVED` | cite the target or command that replaced it, or rewrite or delete the sentence if the step is gone; then rerun npx --no cockpit docs check --config <your cockpit.toml> |
237
255
  | `DOCUMENT_ESTATE_TOPOLOGY_MISMATCH` | declare one sibling for each repository the message says init observed, with that slug and dir, and no other; add any other repository after init with npx --no cockpit sibling add <slug> --config <your cockpit.toml> |
238
256
  | `DOCUMENT_GUARD_HOSTS_NONE` | list the hosts in guards.hosted_db_hosts and guards.vendor_endpoint_hosts and run npx --no cockpit sync --config <your cockpit.toml> once a non-local environment exists |
239
257
  | `DOCUMENT_RUNTIME_NOT_DECLARED` | once a product runs, declare environment.compose_files and run npx --no cockpit sync --config <your cockpit.toml> |
@@ -257,9 +275,11 @@ Every diagnostic message names what failed and how to fix it. These codes always
257
275
  | `LOGS_TRUNCATED` | lower --tail to the number of recent records you need, or read the older records from the container runtime directly |
258
276
  | `MCP_APPROVAL_UNKNOWN` | [see above](#mcp-and-restrictions) |
259
277
  | `MCP_COMMAND_CONFLICT` | [see above](#mcp-and-restrictions) |
278
+ | `MCP_COMMAND_UNRESOLVED` | install the command or correct it in the sibling's MCP declaration, then run npx --no cockpit sync --config <your cockpit.toml>; a changed declaration needs a person to approve its new sha256 under [mcp.approved] |
260
279
  | `MCP_DECLARATION_INVALID` | [see above](#mcp-and-restrictions) |
261
280
  | `MCP_INPUT_INVALID` | [see above](#mcp-and-restrictions) |
262
281
  | `MCP_NOT_APPROVED` | [see above](#mcp-and-restrictions) |
282
+ | `MCP_TRANSPORT_UNAVAILABLE` | [see above](#mcp-and-restrictions) |
263
283
  | `OUTPUT_EXISTS` | choose a new path; replacement requires a future explicit option |
264
284
  | `PORT_FAMILY_COLLISION` | [see above](#common-refusal-guidance) |
265
285
  | `PORT_LEASE_EXHAUSTED` | [see above](#common-refusal-guidance) |
@@ -313,6 +333,7 @@ Every diagnostic message names what failed and how to fix it. These codes always
313
333
  | `SKILL_RENDER_INVENTORY_INVALID` | [see above](#sibling-projection-and-skills) |
314
334
  | `SKILL_STANDING_COST_EXCEEDED` | [see above](#sibling-projection-and-skills) |
315
335
  | `SKILL_TOOL_EQUIVALENCE_MISSING` | [see above](#sibling-projection-and-skills) |
336
+ | `STACKS_ORPHANED` | [see above](#clear-cockpit-state) |
316
337
  | `STATE_FORMAT_UNSUPPORTED` | [see above](#clear-cockpit-state) |
317
338
  | `STATE_RECORD_MALFORMED` | [see above](#clear-cockpit-state) |
318
339
  | `SURFACE_ADOPTION_REFUSED` | [see above](#merge-ownership-and-generation) |
@@ -357,5 +378,5 @@ Every diagnostic message names what failed and how to fix it. These codes always
357
378
  | `UPGRADE_VERSION_INVALID` | give a full version such as 1.2.3, not a range or tag |
358
379
  | `WORK_CHECK_FAILED` | read result.results: fix the product for a check that failed, make one that did not run runnable from the cockpit root, then rerun npx --no cockpit work check --config <your cockpit.toml> --work-order <id> |
359
380
  | `WORK_PERSON_REQUIRED` | stop and ask a person to run the same command with --interactive in a terminal outside CI, read what it shows, and type the digest; never type it for them |
360
- | `WORK_VERIFY_FAILED` | for an order changed since acceptance, run npx --no cockpit work accept --config <your cockpit.toml> --work-order <id> or revert the change; for a done order without a passing grade, regrade it with work grade |
381
+ | `WORK_VERIFY_FAILED` | for an order changed since acceptance, run npx --no cockpit work accept --config <your cockpit.toml> --work-order <id> or revert the change; for a done order without a passing grade, regrade it with work grade; for a decision record that is missing or no longer cites its order, restore it from git or put back its "Work order: <id>" line |
361
382
  <!-- cockpit:diagnostic-index:end -->
package/docs/tutorial.md CHANGED
@@ -145,13 +145,14 @@ with data, add `resettable_volumes` and `seed` as
145
145
 
146
146
  ## Bring the stack up
147
147
 
148
- With Docker running, measure the runtime once with a local image, then start the stack, run one
149
- command against it, and remove it:
148
+ With Docker running, measure the runtime once with a local image, then start the stack, list its
149
+ services, run one command against it, and remove it:
150
150
 
151
151
  ```sh
152
152
  docker pull node:22-alpine
153
153
  ./node_modules/.bin/cockpit runtime measure --config cockpit.toml --image node:22-alpine
154
154
  ./node_modules/.bin/cockpit up --config cockpit.toml
155
+ ./node_modules/.bin/cockpit ps --config cockpit.toml
155
156
  ./node_modules/.bin/cockpit session --config cockpit.toml -- sh -c 'curl -fsS "http://127.0.0.1:$API_HOST_PORT/health"'
156
157
  ./node_modules/.bin/cockpit down --config cockpit.toml
157
158
  ```