@b00dea/cockpit 0.0.0-stage → 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 +22 -2
  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 +74 -4
  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,741 @@
1
+ import { dirname, join, relative, resolve, sep } from 'node:path';
2
+ import { ENVIRONMENT_CODES, errorCause, isCanonicalTimestamp, snapshotJson, } from '../contract.js';
3
+ import { gitEnvironment, resolveLocalRemote } from '../setup.js';
4
+ import { resolvedGitExecutable } from '../executable.js';
5
+ import { authenticateStateNamespace } from '../state.js';
6
+ import { checkoutId as deriveCheckoutId, holderId as deriveHolderId, stackId as deriveStackId } from '../identity.js';
7
+ import { consumeSetupCheckoutProjection, } from '../setup-observation.js';
8
+ import { observeGit } from '../git-observation.js';
9
+ import { exactEnvironment, ownerDiagnostic, sameSource, uniqueDiagnostics } from './shared.js';
10
+ import { consumeEffectiveConfiguration, registryRefusalMessage } from './lifecycle-context.js';
11
+ const issuedSetupRuns = new WeakSet();
12
+ function contextRefusal(target = 'setup', message = 'setup run authority is unavailable or does not match the loaded configuration') {
13
+ return {
14
+ kind: 'refusal', status: 2, result: null,
15
+ diagnostics: [ownerDiagnostic(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, message, target)],
16
+ };
17
+ }
18
+ export function issueSetupRunContext(input) {
19
+ try {
20
+ const checkout = consumeSetupCheckoutProjection(input.checkout);
21
+ const loaded = consumeEffectiveConfiguration(input.configuration);
22
+ const env = exactEnvironment(input.env);
23
+ const executable = resolvedGitExecutable(input.gitExecutable);
24
+ const namespace = authenticateStateNamespace(input.namespace);
25
+ if (loaded.kind === 'refusal')
26
+ return contextRefusal(undefined, registryRefusalMessage(loaded));
27
+ if (!checkout || loaded.kind !== 'complete' || !env || !executable || !namespace)
28
+ return contextRefusal();
29
+ const expectedStackId = deriveStackId({
30
+ cockpitId: checkout.cockpitId,
31
+ checkoutId: checkout.checkoutId,
32
+ variant: { kind: 'shared' },
33
+ });
34
+ const expectedHolderId = deriveHolderId({
35
+ stackId: expectedStackId,
36
+ checkoutId: checkout.checkoutId,
37
+ sessionId: input.sessionId,
38
+ });
39
+ const expectedSiblingsRoot = resolve(dirname(loaded.source.path), loaded.config.cockpit.siblings_root);
40
+ if (!sameSource(checkout.source, loaded.source)
41
+ || loaded.config.cockpit.name !== checkout.cockpitName
42
+ || input.observedAt !== checkout.observedAt
43
+ || Date.now() > Date.parse(checkout.validThrough)
44
+ || input.currentCheckoutPath !== checkout.currentCheckoutPath
45
+ || input.mainCheckoutPath !== checkout.mainCheckoutPath
46
+ || input.cockpitId !== checkout.cockpitId
47
+ || input.checkoutId !== checkout.checkoutId
48
+ || input.stackId !== expectedStackId
49
+ || input.holderId !== expectedHolderId
50
+ || namespace.paths.lifecycle(expectedStackId).cockpitId !== checkout.cockpitId) {
51
+ return contextRefusal(checkout.source.path);
52
+ }
53
+ const config = loaded.config;
54
+ const context = {
55
+ source: {
56
+ path: checkout.source.path,
57
+ byteLength: checkout.source.byteLength,
58
+ sha256: checkout.source.sha256,
59
+ },
60
+ mainCheckoutPath: checkout.mainCheckoutPath,
61
+ siblingsRoot: expectedSiblingsRoot,
62
+ siblings: config.sibling,
63
+ policyCatalog: config.environment.install_policies,
64
+ env,
65
+ gitExecutable: executable,
66
+ namespace,
67
+ stackId: expectedStackId,
68
+ holderId: expectedHolderId,
69
+ writeInstructionPointers: input.writeInstructionPointers === true,
70
+ writeInstructionFiles: input.writeInstructionFiles === true,
71
+ };
72
+ issuedSetupRuns.add(context);
73
+ return { kind: 'complete', status: 0, diagnostics: [], result: context };
74
+ }
75
+ catch (error) {
76
+ return contextRefusal('setup', `setup run authority could not be read: ${errorCause(error)}`);
77
+ }
78
+ }
79
+ export function setupEnvironment(input) {
80
+ let projected;
81
+ try {
82
+ const source = input === undefined
83
+ ? process.env
84
+ : input;
85
+ const git = gitEnvironment(source);
86
+ const xdgStateHome = source.XDG_STATE_HOME;
87
+ projected = {
88
+ ...git,
89
+ ...(xdgStateHome === undefined ? {} : { XDG_STATE_HOME: xdgStateHome }),
90
+ };
91
+ }
92
+ catch {
93
+ // Only reading a caller-supplied environment can throw; the caller refuses it as invalid data.
94
+ return undefined;
95
+ }
96
+ return exactEnvironment(projected);
97
+ }
98
+ function snapshotMatches(source, snapshot) {
99
+ return snapshot.state === 'available' && sameSource(source, snapshot);
100
+ }
101
+ function exactSeedReport(value, kind, siblingRoot, destination) {
102
+ const snapshot = snapshotJson(value);
103
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
104
+ return undefined;
105
+ const report = snapshot;
106
+ if (Object.keys(report).sort().join('\0') !== ['destination', 'kind', 'siblingRoot'].join('\0')
107
+ || report.kind !== kind || report.siblingRoot !== siblingRoot || report.destination !== destination)
108
+ return undefined;
109
+ return { kind, siblingRoot, destination };
110
+ }
111
+ function exactSetupResult(value, sibling, siblingsRoot, outcomeKind, precedingObservation) {
112
+ const snapshot = snapshotJson(value);
113
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
114
+ return undefined;
115
+ const result = snapshot;
116
+ const observed = result.observed;
117
+ const action = result.action;
118
+ const observation = snapshotJson(precedingObservation);
119
+ if (!observation || typeof observation !== 'object' || Array.isArray(observation))
120
+ return undefined;
121
+ const observedSnapshot = observation;
122
+ if (observedSnapshot.state !== 'available'
123
+ || !observedSnapshot.value || typeof observedSnapshot.value !== 'object'
124
+ || Array.isArray(observedSnapshot.value))
125
+ return undefined;
126
+ const evidence = observedSnapshot.value;
127
+ const observedKind = evidence.kind;
128
+ const origin = evidence.remotes && typeof evidence.remotes === 'object' && !Array.isArray(evidence.remotes)
129
+ ? evidence.remotes.origin
130
+ : undefined;
131
+ const originResolution = typeof origin === 'string' ? resolveLocalRemote(origin, siblingsRoot) : undefined;
132
+ const declaredResolution = sibling.remote === undefined ? undefined : resolveLocalRemote(sibling.remote, siblingsRoot);
133
+ const remoteMatches = originResolution?.kind === 'resolved' && declaredResolution?.kind === 'resolved'
134
+ && originResolution.remote === declaredResolution.remote;
135
+ const allowed = outcomeKind === 'complete' && action === 'report' && (sibling.acquire === 'none' && ['absent', 'directory', 'repository'].includes(observedKind)
136
+ || sibling.acquire === 'manual' && observedKind === 'absent'
137
+ || observedKind === 'repository' && remoteMatches) || outcomeKind === 'preview' && (action === 'clone' && sibling.acquire === 'auto' && observedKind === 'absent' && evidence.missingParent === undefined
138
+ || action === 'wire-remote' && sibling.acquire === 'manual' && observedKind === 'repository' && !remoteMatches);
139
+ if (Object.keys(result).sort().join('\0') !== ['action', 'changed', 'observed', 'sibling', 'target'].join('\0')
140
+ || result.sibling !== sibling.slug || result.target !== resolve(siblingsRoot, sibling.dir)
141
+ || result.observed !== observedKind || !allowed
142
+ || !['absent', 'directory', 'repository', 'symlink'].includes(observed)
143
+ || !['clone', 'wire-remote', 'report'].includes(action)
144
+ || typeof result.changed !== 'boolean'
145
+ || result.changed !== (action !== 'report')
146
+ || outcomeKind === 'complete' && action !== 'report'
147
+ || outcomeKind === 'preview' && !(action === 'clone' && observed === 'absent'
148
+ || action === 'wire-remote' && observed === 'repository'))
149
+ return undefined;
150
+ return {
151
+ sibling: sibling.slug,
152
+ target: result.target,
153
+ observed: observed,
154
+ action: action,
155
+ changed: result.changed,
156
+ };
157
+ }
158
+ function exactInstructionPointerResult(value, cockpitRoot, siblingRoot) {
159
+ const snapshot = snapshotJson(value);
160
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
161
+ return undefined;
162
+ const result = snapshot;
163
+ const path = resolve(siblingRoot, 'AGENTS.md');
164
+ if (result.kind === 'refusal') {
165
+ if (Object.keys(result).sort().join('\0') !== ['kind', 'path', 'reason'].join('\0')
166
+ || result.path !== path || typeof result.reason !== 'string' || result.reason.length === 0
167
+ || Buffer.byteLength(result.reason) > 4_096 || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/u.test(result.reason))
168
+ return undefined;
169
+ return { kind: 'refusal', path, reason: result.reason };
170
+ }
171
+ const target = relative(siblingRoot, resolve(cockpitRoot, 'AGENTS.md')).split(sep).join('/');
172
+ if (Object.keys(result).sort().join('\0') !== ['kind', 'path', 'target'].join('\0')
173
+ || !['offered', 'written', 'unchanged'].includes(result.kind)
174
+ || result.path !== path || result.target !== target)
175
+ return undefined;
176
+ return { kind: result.kind, path, target };
177
+ }
178
+ function exactInstructionFilesResult(value, siblingRoot) {
179
+ const snapshot = snapshotJson(value);
180
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
181
+ return undefined;
182
+ const result = snapshot;
183
+ const keys = Object.keys(result).sort().join('\0');
184
+ if (result.root !== siblingRoot)
185
+ return undefined;
186
+ if (result.kind === 'none' && keys === ['kind', 'root'].join('\0'))
187
+ return { kind: 'none', root: siblingRoot };
188
+ if (result.kind === 'refusal') {
189
+ if (keys !== ['kind', 'reason', 'root'].join('\0') || typeof result.reason !== 'string' || result.reason.length === 0
190
+ || Buffer.byteLength(result.reason) > 4_096 || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/u.test(result.reason))
191
+ return undefined;
192
+ return { kind: 'refusal', root: siblingRoot, reason: result.reason };
193
+ }
194
+ if (keys !== ['change', 'kind', 'root'].join('\0') || (result.kind !== 'offered' && result.kind !== 'written')
195
+ || (result.change !== 'claude-import' && result.change !== 'move-to-agents'))
196
+ return undefined;
197
+ return { kind: result.kind, root: siblingRoot, change: result.change };
198
+ }
199
+ function exactSeedIdentity(value) {
200
+ const snapshot = snapshotJson(value);
201
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)
202
+ || Object.keys(snapshot).sort().join('\0') !== ['ctimeNs', 'dev', 'ino', 'mode', 'mtimeNs', 'size'].join('\0')) {
203
+ return undefined;
204
+ }
205
+ const identity = snapshot;
206
+ if (Object.values(identity).some((field) => typeof field !== 'string' || !/^\d+$/.test(field)))
207
+ return undefined;
208
+ return {
209
+ dev: identity.dev,
210
+ ino: identity.ino,
211
+ mode: identity.mode,
212
+ size: identity.size,
213
+ mtimeNs: identity.mtimeNs,
214
+ ctimeNs: identity.ctimeNs,
215
+ };
216
+ }
217
+ function exactSeedPlan(value, siblingRoot, environmentFile) {
218
+ const snapshot = snapshotJson(value);
219
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
220
+ return undefined;
221
+ const plan = snapshot;
222
+ const source = resolve(siblingRoot, environmentFile.template);
223
+ const destination = resolve(siblingRoot, environmentFile.instance);
224
+ if (plan.kind === 'unchanged') {
225
+ return Object.keys(plan).sort().join('\0') === ['destination', 'kind', 'siblingRoot'].join('\0')
226
+ && plan.siblingRoot === siblingRoot && plan.destination === destination
227
+ ? { kind: 'unchanged', siblingRoot, destination }
228
+ : undefined;
229
+ }
230
+ if (plan.kind === 'refusal') {
231
+ const authenticRefusal = plan.code === ENVIRONMENT_CODES.TEMPLATE_NOT_FOUND
232
+ ? plan.path === source
233
+ : plan.code === ENVIRONMENT_CODES.TEMPLATE_PATH_OUTSIDE_ROOT
234
+ && [siblingRoot, source, destination].includes(plan.path);
235
+ return Object.keys(plan).sort().join('\0') === ['code', 'kind', 'path'].join('\0')
236
+ && typeof plan.path === 'string'
237
+ && authenticRefusal
238
+ ? { kind: 'refusal', code: plan.code, path: plan.path }
239
+ : undefined;
240
+ }
241
+ if (plan.kind !== 'ready'
242
+ || Object.keys(plan).sort().join('\0') !== [
243
+ 'canonicalRoot', 'destination', 'destinationParentIdentity', 'kind', 'rootIdentity',
244
+ 'siblingRoot', 'source', 'sourceIdentity',
245
+ ].join('\0')
246
+ || plan.siblingRoot !== siblingRoot || plan.canonicalRoot !== siblingRoot
247
+ || plan.source !== source || plan.destination !== destination)
248
+ return undefined;
249
+ const rootIdentity = exactSeedIdentity(plan.rootIdentity);
250
+ const sourceIdentity = exactSeedIdentity(plan.sourceIdentity);
251
+ const destinationParentIdentity = exactSeedIdentity(plan.destinationParentIdentity);
252
+ return rootIdentity && sourceIdentity && destinationParentIdentity
253
+ ? {
254
+ kind: 'ready', siblingRoot, canonicalRoot: siblingRoot, source, destination,
255
+ rootIdentity, sourceIdentity, destinationParentIdentity,
256
+ }
257
+ : undefined;
258
+ }
259
+ function exactSeedOutcome(value, siblingRoot, source, destination) {
260
+ const snapshot = snapshotJson(value);
261
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot))
262
+ return undefined;
263
+ const result = snapshot;
264
+ if (result.kind === 'created' || result.kind === 'unchanged') {
265
+ return exactSeedReport(result, result.kind, siblingRoot, destination);
266
+ }
267
+ const authenticRefusal = result.code === ENVIRONMENT_CODES.TEMPLATE_PATH_OUTSIDE_ROOT
268
+ ? [source, destination].includes(result.path)
269
+ : result.code === ENVIRONMENT_CODES.ENVIRONMENT_SEED_PRECONDITION_CHANGED
270
+ ? result.path === source
271
+ : result.code === ENVIRONMENT_CODES.ENVIRONMENT_SEED_FAILED && result.path === destination;
272
+ return result.kind === 'refusal'
273
+ && Object.keys(result).sort().join('\0') === ['code', 'kind', 'path'].join('\0')
274
+ && typeof result.path === 'string'
275
+ && authenticRefusal
276
+ ? { kind: 'refusal', code: result.code, path: result.path }
277
+ : undefined;
278
+ }
279
+ // A failed clone is the step a person most often has to fix by hand, and the executor's generic
280
+ // step failure named neither the remote nor git's reason, nor that earlier clones had succeeded.
281
+ function cloneFailureDiagnostics(failed, sibling, siblingsRoot, completedStepIds) {
282
+ const { id, started, stderr } = failed.execution.failedStep;
283
+ // Only a git that ran and exited non-zero failed to clone; a timeout, an abort or an unconfirmed
284
+ // termination keeps the executor's own code, because cloning by hand could race a live git.
285
+ const own = failed.diagnostics.find(diagnostic => diagnostic.target === id);
286
+ if (id !== `setup:${sibling.slug}:clone` || !started || own?.code !== ENVIRONMENT_CODES.EXECUTION_STEP_FAILED)
287
+ return failed.diagnostics;
288
+ const cloned = completedStepIds.flatMap(step => /^setup:(.+):clone$/.exec(step)?.[1] ?? []);
289
+ const target = resolve(siblingsRoot, sibling.dir);
290
+ return [
291
+ {
292
+ severity: 'error',
293
+ code: ENVIRONMENT_CODES.SETUP_CLONE_FAILED,
294
+ message: `git could not clone ${sibling.remote?.replace(/(\w+:\/\/)[^/@\s]+@/, '$1[redacted]@')} into ${target}: ${stderr ?? 'git reported no error text'}`
295
+ + (cloned.length === 0 ? '' : `; already cloned and left in place: ${cloned.join(', ')}`),
296
+ target,
297
+ fix: 'check that this machine can reach the remote (git ls-remote <remote>) and that the sibling remote in cockpit.toml is right, or clone it into the named directory yourself; then run npx --no cockpit setup --config <your cockpit.toml> again',
298
+ },
299
+ ...failed.diagnostics.filter(diagnostic => diagnostic.target !== id),
300
+ ];
301
+ }
302
+ function configChangedDiagnostic(source) {
303
+ return {
304
+ severity: 'error',
305
+ code: ENVIRONMENT_CODES.CONFIG_CHANGED_DURING_EXECUTION,
306
+ message: 'cockpit configuration changed during setup execution',
307
+ target: source.path,
308
+ };
309
+ }
310
+ function refusal(diagnostics, evidence) {
311
+ return { kind: 'refusal', status: 2, diagnostics: uniqueDiagnostics(diagnostics), result: null, evidence };
312
+ }
313
+ function executionEvidence(completedStepIds, mutationMayHaveOccurred, failed) {
314
+ return failed
315
+ ? {
316
+ mutationMayHaveOccurred: mutationMayHaveOccurred
317
+ || failed.execution.failedStep.started
318
+ || completedStepIds.length > 0,
319
+ completedStepIds,
320
+ failedStep: { id: failed.execution.failedStep.id, started: failed.execution.failedStep.started },
321
+ compensation: failed.execution.compensation,
322
+ }
323
+ : {
324
+ mutationMayHaveOccurred,
325
+ completedStepIds,
326
+ compensation: { attemptedStepIds: [], completedStepIds: [], failed: [] },
327
+ };
328
+ }
329
+ async function sequenceRefusal(context, adapters, diagnostics, evidence) {
330
+ const after = await adapters.observeSource(context.source.path);
331
+ const allDiagnostics = [...diagnostics];
332
+ if (!snapshotMatches(context.source, after))
333
+ allDiagnostics.push(configChangedDiagnostic(context.source));
334
+ return refusal(allDiagnostics, { execution: evidence, source: { before: context.source, after } });
335
+ }
336
+ function attributePlannerDiagnostics(diagnostics, sourcePath) {
337
+ return diagnostics.map((diagnostic) => {
338
+ const originalTarget = diagnostic.target;
339
+ const suffix = originalTarget && originalTarget !== sourcePath ? ` (${originalTarget})` : '';
340
+ return {
341
+ ...diagnostic,
342
+ message: suffix && !diagnostic.message.endsWith(suffix)
343
+ ? `${diagnostic.message}${suffix}`
344
+ : diagnostic.message,
345
+ target: sourcePath,
346
+ };
347
+ });
348
+ }
349
+ function exactIgnoreRefusal(value, environmentFile) {
350
+ const snapshot = snapshotJson(value);
351
+ if (snapshot && typeof snapshot === 'object' && !Array.isArray(snapshot)
352
+ && Object.keys(snapshot).sort().join('\0') === ['code', 'kind', 'path'].join('\0')) {
353
+ const refusal = snapshot;
354
+ if (refusal.kind === 'refusal'
355
+ && [
356
+ ENVIRONMENT_CODES.TEMPLATE_NOT_TRACKED,
357
+ ENVIRONMENT_CODES.TEMPLATE_IGNORED,
358
+ ENVIRONMENT_CODES.INSTANCE_NOT_IGNORED,
359
+ ].includes(refusal.code)
360
+ && refusal.path === (refusal.code === ENVIRONMENT_CODES.INSTANCE_NOT_IGNORED
361
+ ? environmentFile.instance
362
+ : environmentFile.template)) {
363
+ return {
364
+ kind: 'refusal',
365
+ code: refusal.code,
366
+ path: refusal.path,
367
+ };
368
+ }
369
+ }
370
+ return undefined;
371
+ }
372
+ function ignoreDiagnostic(value, environmentFile, unavailableTarget, refusalMessage) {
373
+ if (value === true)
374
+ return undefined;
375
+ const refusal = exactIgnoreRefusal(value, environmentFile);
376
+ if (refusal)
377
+ return ownerDiagnostic(refusal.code, refusalMessage, refusal.path);
378
+ return ownerDiagnostic(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'environment ignore evidence is unavailable', unavailableTarget);
379
+ }
380
+ function exactPolicyBindings(value, context, siblingIndex, sibling, checkoutRoot, checkoutId) {
381
+ const snapshot = snapshotJson(value);
382
+ if (!Array.isArray(snapshot) || snapshot.length !== 1)
383
+ return undefined;
384
+ const binding = snapshot[0];
385
+ if (!binding || typeof binding !== 'object' || Array.isArray(binding))
386
+ return undefined;
387
+ const candidate = binding;
388
+ const catalogIndex = candidate.diagnostics?.catalogIndex;
389
+ const policy = Number.isSafeInteger(catalogIndex) ? context.policyCatalog[catalogIndex] : undefined;
390
+ if (Object.keys(binding).sort().join('\0') !== [
391
+ 'checkoutId', 'checkoutRoot', 'diagnostics', 'policy', 'siblingIndex', 'siblingSlug', 'source',
392
+ ].sort().join('\0')
393
+ || !candidate.source || Object.keys(candidate.source).sort().join('\0') !== ['byteLength', 'path', 'sha256'].join('\0')
394
+ || !candidate.diagnostics || Object.keys(candidate.diagnostics).join('\0') !== 'catalogIndex'
395
+ || !policy || !sameSource(candidate.source, context.source)
396
+ || candidate.siblingIndex !== siblingIndex || candidate.siblingSlug !== sibling.slug
397
+ || candidate.checkoutRoot !== checkoutRoot || candidate.checkoutId !== checkoutId
398
+ || JSON.stringify(snapshotJson(candidate.policy)) !== JSON.stringify(snapshotJson(policy)))
399
+ return undefined;
400
+ return [{
401
+ source: context.source,
402
+ siblingIndex,
403
+ diagnostics: { catalogIndex: catalogIndex },
404
+ siblingSlug: sibling.slug,
405
+ checkoutRoot,
406
+ checkoutId,
407
+ policy: snapshotJson(policy),
408
+ }];
409
+ }
410
+ export async function runSetup(input, adapters) {
411
+ const loaded = await adapters.load(input);
412
+ if (loaded.kind === 'refusal')
413
+ return loaded;
414
+ if (loaded.kind === 'preview') {
415
+ return {
416
+ kind: 'refusal', status: 2, result: null,
417
+ diagnostics: [ownerDiagnostic(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'setup context was not fully resolved', input.configPath)],
418
+ };
419
+ }
420
+ const context = loaded.result;
421
+ if (!issuedSetupRuns.has(context))
422
+ return contextRefusal();
423
+ issuedSetupRuns.delete(context);
424
+ const diagnostics = [...loaded.diagnostics];
425
+ const siblings = [];
426
+ const environmentFiles = [];
427
+ const policies = [];
428
+ const completedStepIds = [];
429
+ let mutationMayHaveOccurred = false;
430
+ let lifecycleStarted = false;
431
+ const refuseSourceDrift = async () => {
432
+ const after = await adapters.observeSource(context.source.path);
433
+ return snapshotMatches(context.source, after)
434
+ ? undefined
435
+ : refusal([...diagnostics, configChangedDiagnostic(context.source)], {
436
+ execution: executionEvidence(completedStepIds, mutationMayHaveOccurred),
437
+ source: {
438
+ before: context.source,
439
+ after,
440
+ ...(after.state === 'available'
441
+ ? { postcheck: { path: after.path, byteLength: after.byteLength, sha256: after.sha256 } }
442
+ : {}),
443
+ },
444
+ });
445
+ };
446
+ const nextOperationTimestamp = async () => {
447
+ let observedAt;
448
+ let cause = '';
449
+ try {
450
+ observedAt = adapters.observedAt();
451
+ }
452
+ catch (error) {
453
+ cause = `: ${errorCause(error)}`;
454
+ }
455
+ return isCanonicalTimestamp(observedAt)
456
+ ? observedAt
457
+ : sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, `setup operation timestamp is unavailable${cause}`, context.source.path)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
458
+ };
459
+ const startLifecycle = async () => {
460
+ if (lifecycleStarted)
461
+ return undefined;
462
+ const sourceFailure = await refuseSourceDrift();
463
+ if (sourceFailure)
464
+ return sourceFailure;
465
+ const observedAt = await nextOperationTimestamp();
466
+ if (typeof observedAt !== 'string')
467
+ return observedAt;
468
+ const transition = await adapters.recordStarted({
469
+ namespace: context.namespace,
470
+ stackId: context.stackId,
471
+ holderId: context.holderId,
472
+ observedAt,
473
+ });
474
+ if (transition.kind === 'refusal') {
475
+ return sequenceRefusal(context, adapters, [...diagnostics, ...transition.diagnostics], executionEvidence(completedStepIds, mutationMayHaveOccurred));
476
+ }
477
+ mutationMayHaveOccurred ||= transition.kind === 'complete';
478
+ lifecycleStarted = true;
479
+ return undefined;
480
+ };
481
+ for (const [siblingIndex, sibling] of context.siblings.entries()) {
482
+ const siblingInput = { context, siblingIndex, sibling };
483
+ const observation = await adapters.observeGit(siblingInput);
484
+ const planned = await adapters.planGit({ ...siblingInput, observation });
485
+ if (planned.kind === 'refusal') {
486
+ return sequenceRefusal(context, adapters, [...diagnostics, ...attributePlannerDiagnostics(planned.diagnostics, context.source.path)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
487
+ }
488
+ const plannedResult = exactSetupResult(planned.result, sibling, context.siblingsRoot, planned.kind, observation);
489
+ if (!plannedResult) {
490
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'setup plan returned an invalid repository report', resolve(context.siblingsRoot, sibling.dir))], executionEvidence(completedStepIds, mutationMayHaveOccurred));
491
+ }
492
+ diagnostics.push(...planned.diagnostics);
493
+ let siblingResult = plannedResult;
494
+ if (planned.kind === 'preview') {
495
+ const lifecycleFailure = await startLifecycle();
496
+ if (lifecycleFailure)
497
+ return lifecycleFailure;
498
+ const sourceFailure = await refuseSourceDrift();
499
+ if (sourceFailure)
500
+ return sourceFailure;
501
+ const execution = await adapters.execute(planned.plan);
502
+ if (execution.kind === 'refusal') {
503
+ return sequenceRefusal(context, adapters, [...diagnostics, ...cloneFailureDiagnostics(execution, sibling, context.siblingsRoot, completedStepIds)], executionEvidence([...completedStepIds, ...execution.execution.completedStepIds], mutationMayHaveOccurred, execution));
504
+ }
505
+ const stepIds = execution.result.steps.map(({ id }) => id);
506
+ completedStepIds.push(...stepIds);
507
+ mutationMayHaveOccurred ||= stepIds.length > 0;
508
+ const checkedObservation = await adapters.observeGit(siblingInput);
509
+ const checked = await adapters.planGit({ ...siblingInput, observation: checkedObservation });
510
+ const after = await adapters.observeSource(context.source.path);
511
+ const checkedResult = checked.kind === 'complete'
512
+ ? exactSetupResult(checked.result, sibling, context.siblingsRoot, 'complete', checkedObservation)
513
+ : undefined;
514
+ if (!snapshotMatches(context.source, after) || !checkedResult) {
515
+ const checkedDiagnostics = checked.kind === 'refusal'
516
+ ? attributePlannerDiagnostics(checked.diagnostics, context.source.path)
517
+ : checked.diagnostics;
518
+ return refusal(uniqueDiagnostics([
519
+ ...diagnostics,
520
+ ...checkedDiagnostics,
521
+ !snapshotMatches(context.source, after)
522
+ ? configChangedDiagnostic(context.source)
523
+ : ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'setup execution completed but its postcheck did not verify the declared state', context.source.path),
524
+ ]), {
525
+ execution: executionEvidence(completedStepIds, mutationMayHaveOccurred),
526
+ source: {
527
+ before: context.source,
528
+ after,
529
+ ...(checked.kind !== 'refusal' && after.state === 'available'
530
+ ? { postcheck: { path: after.path, byteLength: after.byteLength, sha256: after.sha256 } }
531
+ : {}),
532
+ },
533
+ });
534
+ }
535
+ diagnostics.push(...checked.diagnostics);
536
+ siblingResult = checkedResult;
537
+ }
538
+ // The cockpit's own directory gets its AGENTS.md and CLAUDE.md from sync, not from setup, and a
539
+ // checkout setup leaves absent (acquire = "none") has no files to change.
540
+ if (siblingResult.target !== dirname(context.source.path) && siblingResult.observed !== 'absent') {
541
+ if (context.writeInstructionFiles) {
542
+ const lifecycleFailure = await startLifecycle();
543
+ if (lifecycleFailure)
544
+ return lifecycleFailure;
545
+ const sourceFailure = await refuseSourceDrift();
546
+ if (sourceFailure)
547
+ return sourceFailure;
548
+ }
549
+ const files = exactInstructionFilesResult(await adapters.syncInstructionFiles({
550
+ siblingRoot: siblingResult.target,
551
+ write: context.writeInstructionFiles,
552
+ }), siblingResult.target);
553
+ if (!files || files.kind === 'refusal') {
554
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_INSTRUCTION_FILES_INVALID, files ? files.reason : 'sibling instruction file result was not bound to the declared sibling', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
555
+ }
556
+ if (files.kind === 'offered')
557
+ diagnostics.push({
558
+ severity: 'info',
559
+ code: ENVIRONMENT_CODES.SETUP_INSTRUCTION_FILES_OFFERED,
560
+ message: files.change === 'claude-import'
561
+ ? `sibling ${sibling.slug} has AGENTS.md but no CLAUDE.md; setup would create CLAUDE.md holding the single line @AGENTS.md, so Claude Code loads the same instructions as Codex and OpenCode`
562
+ : `sibling ${sibling.slug} has CLAUDE.md but no AGENTS.md; setup would copy CLAUDE.md byte for byte to AGENTS.md and replace CLAUDE.md with the single line @AGENTS.md, so Codex and OpenCode load the same instructions as Claude Code`,
563
+ target: siblingResult.target,
564
+ fix: 'review the proposed change with a person, then run npx --no cockpit setup --config <your cockpit.toml> --write-instruction-files',
565
+ });
566
+ mutationMayHaveOccurred ||= files.kind === 'written';
567
+ }
568
+ if (sibling.instruction_pointer) {
569
+ if (!adapters.syncInstructionPointer) {
570
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_INSTRUCTION_POINTER_INVALID, 'instruction pointer support is unavailable', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
571
+ }
572
+ if (context.writeInstructionPointers) {
573
+ const lifecycleFailure = await startLifecycle();
574
+ if (lifecycleFailure)
575
+ return lifecycleFailure;
576
+ const sourceFailure = await refuseSourceDrift();
577
+ if (sourceFailure)
578
+ return sourceFailure;
579
+ }
580
+ const pointer = exactInstructionPointerResult(await adapters.syncInstructionPointer({
581
+ cockpitRoot: dirname(context.source.path),
582
+ siblingRoot: siblingResult.target,
583
+ write: context.writeInstructionPointers,
584
+ }), dirname(context.source.path), siblingResult.target);
585
+ if (!pointer) {
586
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_INSTRUCTION_POINTER_INVALID, 'instruction pointer result was not bound to the declared sibling guide', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
587
+ }
588
+ if (pointer.kind === 'refusal') {
589
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_INSTRUCTION_POINTER_INVALID, pointer.reason, pointer.path)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
590
+ }
591
+ if (pointer.kind === 'offered')
592
+ diagnostics.push({
593
+ severity: 'info',
594
+ code: ENVIRONMENT_CODES.SETUP_INSTRUCTION_POINTER_OFFERED,
595
+ message: 'the declared sibling has no cockpit instruction pointer',
596
+ target: pointer.path,
597
+ fix: 'review the proposed AGENTS.md block, then run npx --no cockpit setup --config <your cockpit.toml> --write-instruction-pointers',
598
+ });
599
+ mutationMayHaveOccurred ||= pointer.kind === 'written';
600
+ }
601
+ for (const [environmentFileIndex, environmentFile] of (sibling.environment_files ?? []).entries()) {
602
+ const ignoreInput = { ...siblingInput, environmentFileIndex, environmentFile };
603
+ const expectedSiblingRoot = siblingResult.target;
604
+ const expectedDestination = resolve(expectedSiblingRoot, environmentFile.instance);
605
+ const initialObservedAt = await nextOperationTimestamp();
606
+ if (typeof initialObservedAt !== 'string')
607
+ return initialObservedAt;
608
+ const initialAuthority = await adapters.withIgnore({
609
+ ...ignoreInput,
610
+ observedAt: initialObservedAt,
611
+ }, () => true);
612
+ const initialAuthorityDiagnostic = ignoreDiagnostic(initialAuthority, environmentFile, siblingResult.target, 'environment instance could not be seeded');
613
+ if (initialAuthorityDiagnostic) {
614
+ return sequenceRefusal(context, adapters, [...diagnostics, initialAuthorityDiagnostic], executionEvidence(completedStepIds, mutationMayHaveOccurred));
615
+ }
616
+ let seedPlan = exactSeedPlan(await adapters.planSeed(ignoreInput), expectedSiblingRoot, environmentFile);
617
+ if (!seedPlan) {
618
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'environment instance plan did not match the declared state', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
619
+ }
620
+ if (seedPlan.kind === 'refusal') {
621
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(seedPlan.code, 'environment instance could not be seeded', seedPlan.path)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
622
+ }
623
+ if (seedPlan.kind === 'unchanged') {
624
+ const report = exactSeedReport(seedPlan, 'unchanged', expectedSiblingRoot, expectedDestination);
625
+ if (!report) {
626
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'environment instance report did not match the declared state', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
627
+ }
628
+ const finalObservedAt = await nextOperationTimestamp();
629
+ if (typeof finalObservedAt !== 'string')
630
+ return finalObservedAt;
631
+ const finalAuthority = await adapters.withIgnore({
632
+ ...ignoreInput,
633
+ observedAt: finalObservedAt,
634
+ }, () => true);
635
+ const finalAuthorityDiagnostic = ignoreDiagnostic(finalAuthority, environmentFile, siblingResult.target, 'environment instance could not be verified');
636
+ if (finalAuthorityDiagnostic) {
637
+ return sequenceRefusal(context, adapters, [...diagnostics, finalAuthorityDiagnostic], executionEvidence(completedStepIds, mutationMayHaveOccurred));
638
+ }
639
+ environmentFiles.push(report);
640
+ }
641
+ if (seedPlan.kind === 'ready') {
642
+ const readyPlan = seedPlan;
643
+ const lifecycleFailure = await startLifecycle();
644
+ if (lifecycleFailure)
645
+ return lifecycleFailure;
646
+ const sourceFailure = await refuseSourceDrift();
647
+ if (sourceFailure)
648
+ return sourceFailure;
649
+ const seedObservedAt = await nextOperationTimestamp();
650
+ if (typeof seedObservedAt !== 'string')
651
+ return seedObservedAt;
652
+ const seededValue = await adapters.withIgnore({
653
+ ...ignoreInput,
654
+ observedAt: seedObservedAt,
655
+ }, () => adapters.seed(readyPlan));
656
+ if (seededValue === undefined) {
657
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_PATH_TYPE_INVALID, 'environment ignore evidence is unavailable', siblingResult.target)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
658
+ }
659
+ const ignoreRefusal = exactIgnoreRefusal(seededValue, environmentFile);
660
+ if (ignoreRefusal) {
661
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ignoreRefusal.code, 'environment instance seed failed', ignoreRefusal.path)], executionEvidence(completedStepIds, true));
662
+ }
663
+ const seeded = exactSeedOutcome(seededValue, expectedSiblingRoot, readyPlan.source, expectedDestination);
664
+ if (!seeded) {
665
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'environment instance seed returned an invalid report', siblingResult.target)], executionEvidence(completedStepIds, true));
666
+ }
667
+ if (seeded.kind === 'refusal') {
668
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(seeded.code, 'environment instance seed failed', seeded.path)], executionEvidence(completedStepIds, true));
669
+ }
670
+ mutationMayHaveOccurred ||= seeded.kind === 'created';
671
+ seedPlan = exactSeedPlan(await adapters.planSeed(ignoreInput), expectedSiblingRoot, environmentFile);
672
+ const postcheckReport = seedPlan?.kind === 'unchanged'
673
+ ? exactSeedReport(seedPlan, 'unchanged', expectedSiblingRoot, expectedDestination)
674
+ : undefined;
675
+ const finalObservedAt = await nextOperationTimestamp();
676
+ if (typeof finalObservedAt !== 'string')
677
+ return finalObservedAt;
678
+ const finalAuthority = await adapters.withIgnore({
679
+ ...ignoreInput,
680
+ observedAt: finalObservedAt,
681
+ }, () => true);
682
+ const afterSeed = await adapters.observeSource(context.source.path);
683
+ if (!snapshotMatches(context.source, afterSeed) || !postcheckReport
684
+ || finalAuthority !== true) {
685
+ const authorityDiagnostic = ignoreDiagnostic(finalAuthority, environmentFile, siblingResult.target, 'environment instance could not be verified');
686
+ return sequenceRefusal(context, adapters, [
687
+ ...diagnostics,
688
+ ...(authorityDiagnostic ? [authorityDiagnostic] : []),
689
+ ...(!snapshotMatches(context.source, afterSeed)
690
+ ? [configChangedDiagnostic(context.source)]
691
+ : []),
692
+ ...(!postcheckReport
693
+ ? [ownerDiagnostic(ENVIRONMENT_CODES.SETUP_POSTCHECK_FAILED, 'environment instance postcheck did not verify the declared state', siblingResult.target)]
694
+ : []),
695
+ ], executionEvidence(completedStepIds, mutationMayHaveOccurred));
696
+ }
697
+ environmentFiles.push(seeded);
698
+ }
699
+ }
700
+ const checkoutRoot = siblingResult.target;
701
+ const checkoutId = deriveCheckoutId({ checkoutPath: checkoutRoot });
702
+ if (sibling.toolchain !== 'none') {
703
+ const observedAt = await nextOperationTimestamp();
704
+ if (typeof observedAt !== 'string')
705
+ return observedAt;
706
+ const policyInput = { ...siblingInput, checkoutRoot, checkoutId, observedAt };
707
+ const policyObservation = await adapters.observePolicy(policyInput);
708
+ const resolution = await adapters.withPolicy(policyObservation, policyInput, (projection) => adapters.resolvePolicy({ ...siblingInput, checkoutRoot, checkoutId, projection }));
709
+ if (resolution === undefined || resolution.kind === 'unavailable' || resolution.kind === 'refusal') {
710
+ const code = resolution?.code ?? ENVIRONMENT_CODES.INSTALL_POLICY_OBSERVATION_UNAVAILABLE;
711
+ const message = resolution?.kind === 'refusal'
712
+ ? 'declared dependency policy could not be resolved exactly'
713
+ : resolution?.reason ?? 'dependency policy evidence is unavailable';
714
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(code, message, checkoutRoot)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
715
+ }
716
+ const bindings = exactPolicyBindings(resolution.bindings, context, siblingIndex, sibling, checkoutRoot, checkoutId);
717
+ if (!bindings) {
718
+ return sequenceRefusal(context, adapters, [...diagnostics, ownerDiagnostic(ENVIRONMENT_CODES.INSTALL_POLICY_OBSERVATION_UNAVAILABLE, 'dependency policy binding does not match the sibling authority', checkoutRoot)], executionEvidence(completedStepIds, mutationMayHaveOccurred));
719
+ }
720
+ policies.push(...bindings);
721
+ }
722
+ siblings.push(siblingResult);
723
+ }
724
+ const sourceFailure = await refuseSourceDrift();
725
+ if (sourceFailure)
726
+ return sourceFailure;
727
+ return {
728
+ kind: 'complete',
729
+ status: 0,
730
+ diagnostics: uniqueDiagnostics(diagnostics),
731
+ result: {
732
+ source: context.source,
733
+ siblings,
734
+ ...(environmentFiles.length > 0 ? { environmentFiles } : {}),
735
+ ...(policies.length > 0 ? { installPolicies: policies } : {}),
736
+ ...(mutationMayHaveOccurred
737
+ ? { execution: executionEvidence(completedStepIds, mutationMayHaveOccurred) }
738
+ : {}),
739
+ },
740
+ };
741
+ }