@b00dea/cockpit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE +21 -0
  3. package/README.md +25 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2644 -0
  6. package/dist/autonomy.d.ts +45 -0
  7. package/dist/autonomy.js +173 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +117 -0
  11. package/dist/browser/commands.js +453 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +118 -0
  17. package/dist/browser/runtime.js +186 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +561 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/canonical-json.d.ts +2 -0
  23. package/dist/canonical-json.js +5 -0
  24. package/dist/cli-guard.d.ts +5 -0
  25. package/dist/cli-guard.js +71 -0
  26. package/dist/cli-main.d.ts +176 -0
  27. package/dist/cli-main.js +1584 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.js +16 -0
  30. package/dist/command-table.d.ts +121 -0
  31. package/dist/command-table.js +664 -0
  32. package/dist/config/load.d.ts +183 -0
  33. package/dist/config/load.js +367 -0
  34. package/dist/config/sibling-toml.d.ts +48 -0
  35. package/dist/config/sibling-toml.js +160 -0
  36. package/dist/config/surface-tools-toml.d.ts +9 -0
  37. package/dist/config/surface-tools-toml.js +37 -0
  38. package/dist/config/unconfirmed-toml.d.ts +9 -0
  39. package/dist/config/unconfirmed-toml.js +29 -0
  40. package/dist/config/verified-write.d.ts +43 -0
  41. package/dist/config/verified-write.js +89 -0
  42. package/dist/diagnostics.d.ts +26 -0
  43. package/dist/diagnostics.js +344 -0
  44. package/dist/docs/check.d.ts +16 -0
  45. package/dist/docs/check.js +197 -0
  46. package/dist/docs/decision-record.d.ts +34 -0
  47. package/dist/docs/decision-record.js +135 -0
  48. package/dist/docs/decisions.d.ts +16 -0
  49. package/dist/docs/decisions.js +103 -0
  50. package/dist/docs/markdown.d.ts +36 -0
  51. package/dist/docs/markdown.js +88 -0
  52. package/dist/docs/references.d.ts +32 -0
  53. package/dist/docs/references.js +148 -0
  54. package/dist/emit.d.ts +22 -0
  55. package/dist/emit.js +43 -0
  56. package/dist/entrypoint-kinds.d.ts +3 -0
  57. package/dist/entrypoint-kinds.js +11 -0
  58. package/dist/environment/application/confirm.d.ts +35 -0
  59. package/dist/environment/application/confirm.js +122 -0
  60. package/dist/environment/application/destructive.d.ts +6 -0
  61. package/dist/environment/application/destructive.js +429 -0
  62. package/dist/environment/application/doctor.d.ts +94 -0
  63. package/dist/environment/application/doctor.js +247 -0
  64. package/dist/environment/application/guard.d.ts +49 -0
  65. package/dist/environment/application/guard.js +677 -0
  66. package/dist/environment/application/health.d.ts +143 -0
  67. package/dist/environment/application/health.js +385 -0
  68. package/dist/environment/application/lifecycle-context.d.ts +235 -0
  69. package/dist/environment/application/lifecycle-context.js +154 -0
  70. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  71. package/dist/environment/application/lifecycle-state.js +202 -0
  72. package/dist/environment/application/lifecycle.d.ts +18 -0
  73. package/dist/environment/application/lifecycle.js +737 -0
  74. package/dist/environment/application/logs.d.ts +45 -0
  75. package/dist/environment/application/logs.js +127 -0
  76. package/dist/environment/application/services.d.ts +60 -0
  77. package/dist/environment/application/services.js +89 -0
  78. package/dist/environment/application/setup.d.ts +129 -0
  79. package/dist/environment/application/setup.js +741 -0
  80. package/dist/environment/application/shared.d.ts +11 -0
  81. package/dist/environment/application/shared.js +61 -0
  82. package/dist/environment/application/siblings.d.ts +43 -0
  83. package/dist/environment/application/siblings.js +269 -0
  84. package/dist/environment/claims.d.ts +389 -0
  85. package/dist/environment/claims.js +1701 -0
  86. package/dist/environment/codex-hook-trust.d.ts +11 -0
  87. package/dist/environment/codex-hook-trust.js +185 -0
  88. package/dist/environment/compose-environment.d.ts +1 -0
  89. package/dist/environment/compose-environment.js +6 -0
  90. package/dist/environment/compose-ownership.d.ts +21 -0
  91. package/dist/environment/compose-ownership.js +117 -0
  92. package/dist/environment/compose-topology.d.ts +125 -0
  93. package/dist/environment/compose-topology.js +490 -0
  94. package/dist/environment/contract.d.ts +522 -0
  95. package/dist/environment/contract.js +794 -0
  96. package/dist/environment/declaration.d.ts +14 -0
  97. package/dist/environment/declaration.js +39 -0
  98. package/dist/environment/destructive-composition.d.ts +29 -0
  99. package/dist/environment/destructive-composition.js +333 -0
  100. package/dist/environment/docker-daemon.d.ts +30 -0
  101. package/dist/environment/docker-daemon.js +78 -0
  102. package/dist/environment/docker-endpoint.d.ts +10 -0
  103. package/dist/environment/docker-endpoint.js +19 -0
  104. package/dist/environment/env-schema-observation.d.ts +46 -0
  105. package/dist/environment/env-schema-observation.js +241 -0
  106. package/dist/environment/evidence.d.ts +136 -0
  107. package/dist/environment/evidence.js +437 -0
  108. package/dist/environment/executable.d.ts +38 -0
  109. package/dist/environment/executable.js +190 -0
  110. package/dist/environment/generated-application/authorities.d.ts +5 -0
  111. package/dist/environment/generated-application/authorities.js +61 -0
  112. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  113. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  114. package/dist/environment/generated-application/check.d.ts +13 -0
  115. package/dist/environment/generated-application/check.js +191 -0
  116. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  117. package/dist/environment/generated-application/custom-surface.js +289 -0
  118. package/dist/environment/generated-application/prepare.d.ts +29 -0
  119. package/dist/environment/generated-application/prepare.js +278 -0
  120. package/dist/environment/generated-application/publication.d.ts +33 -0
  121. package/dist/environment/generated-application/publication.js +547 -0
  122. package/dist/environment/generated-application/shared.d.ts +492 -0
  123. package/dist/environment/generated-application/shared.js +354 -0
  124. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  125. package/dist/environment/generated-application/surface-observation.js +69 -0
  126. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  127. package/dist/environment/generated-application/surface-outputs.js +400 -0
  128. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  129. package/dist/environment/generated-application/surfaces.js +704 -0
  130. package/dist/environment/generated-application/sync.d.ts +6 -0
  131. package/dist/environment/generated-application/sync.js +124 -0
  132. package/dist/environment/git-observation.d.ts +202 -0
  133. package/dist/environment/git-observation.js +553 -0
  134. package/dist/environment/health-composition.d.ts +12 -0
  135. package/dist/environment/health-composition.js +645 -0
  136. package/dist/environment/host-observation.d.ts +56 -0
  137. package/dist/environment/host-observation.js +52 -0
  138. package/dist/environment/identity.d.ts +48 -0
  139. package/dist/environment/identity.js +85 -0
  140. package/dist/environment/init-application.d.ts +333 -0
  141. package/dist/environment/init-application.js +726 -0
  142. package/dist/environment/install-policy-observation.d.ts +71 -0
  143. package/dist/environment/install-policy-observation.js +284 -0
  144. package/dist/environment/install-policy.d.ts +48 -0
  145. package/dist/environment/install-policy.js +96 -0
  146. package/dist/environment/instance.d.ts +33 -0
  147. package/dist/environment/instance.js +122 -0
  148. package/dist/environment/instruction-pointer.d.ts +19 -0
  149. package/dist/environment/instruction-pointer.js +144 -0
  150. package/dist/environment/lifecycle-composition.d.ts +31 -0
  151. package/dist/environment/lifecycle-composition.js +742 -0
  152. package/dist/environment/lifecycle-stage.d.ts +120 -0
  153. package/dist/environment/lifecycle-stage.js +408 -0
  154. package/dist/environment/lifecycle.d.ts +75 -0
  155. package/dist/environment/lifecycle.js +354 -0
  156. package/dist/environment/logs.d.ts +132 -0
  157. package/dist/environment/logs.js +519 -0
  158. package/dist/environment/map-limit.d.ts +2 -0
  159. package/dist/environment/map-limit.js +13 -0
  160. package/dist/environment/mcp-observation.d.ts +9 -0
  161. package/dist/environment/mcp-observation.js +46 -0
  162. package/dist/environment/observations.d.ts +46 -0
  163. package/dist/environment/observations.js +346 -0
  164. package/dist/environment/operator-application.d.ts +79 -0
  165. package/dist/environment/operator-application.js +197 -0
  166. package/dist/environment/ports.d.ts +182 -0
  167. package/dist/environment/ports.js +1205 -0
  168. package/dist/environment/posture-observation.d.ts +68 -0
  169. package/dist/environment/posture-observation.js +253 -0
  170. package/dist/environment/presence.d.ts +4 -0
  171. package/dist/environment/presence.js +26 -0
  172. package/dist/environment/read-composition.d.ts +32 -0
  173. package/dist/environment/read-composition.js +397 -0
  174. package/dist/environment/readiness-composition.d.ts +57 -0
  175. package/dist/environment/readiness-composition.js +151 -0
  176. package/dist/environment/reclaim.d.ts +370 -0
  177. package/dist/environment/reclaim.js +1940 -0
  178. package/dist/environment/repository-identity.d.ts +13 -0
  179. package/dist/environment/repository-identity.js +108 -0
  180. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  181. package/dist/environment/runtime-measurement-record.js +111 -0
  182. package/dist/environment/runtime-measurement.d.ts +27 -0
  183. package/dist/environment/runtime-measurement.js +224 -0
  184. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  185. package/dist/environment/runtime-probe-residue.js +148 -0
  186. package/dist/environment/runtime-probe.d.ts +72 -0
  187. package/dist/environment/runtime-probe.js +725 -0
  188. package/dist/environment/runtime.d.ts +551 -0
  189. package/dist/environment/runtime.js +2405 -0
  190. package/dist/environment/seed.d.ts +16 -0
  191. package/dist/environment/seed.js +43 -0
  192. package/dist/environment/selftest-application.d.ts +86 -0
  193. package/dist/environment/selftest-application.js +713 -0
  194. package/dist/environment/sensitive.d.ts +3 -0
  195. package/dist/environment/sensitive.js +48 -0
  196. package/dist/environment/served-checkout-grants.d.ts +53 -0
  197. package/dist/environment/served-checkout-grants.js +182 -0
  198. package/dist/environment/setup-observation.d.ts +62 -0
  199. package/dist/environment/setup-observation.js +1473 -0
  200. package/dist/environment/setup.d.ts +43 -0
  201. package/dist/environment/setup.js +176 -0
  202. package/dist/environment/sibling-checkouts.d.ts +119 -0
  203. package/dist/environment/sibling-checkouts.js +254 -0
  204. package/dist/environment/sibling-instructions.d.ts +33 -0
  205. package/dist/environment/sibling-instructions.js +224 -0
  206. package/dist/environment/sibling-observation.d.ts +27 -0
  207. package/dist/environment/sibling-observation.js +45 -0
  208. package/dist/environment/sibling-pull.d.ts +23 -0
  209. package/dist/environment/sibling-pull.js +95 -0
  210. package/dist/environment/sibling-registration.d.ts +45 -0
  211. package/dist/environment/sibling-registration.js +538 -0
  212. package/dist/environment/stack-inputs.d.ts +18 -0
  213. package/dist/environment/stack-inputs.js +210 -0
  214. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  215. package/dist/environment/stack-inventory-prune.js +129 -0
  216. package/dist/environment/stack-inventory-report.d.ts +44 -0
  217. package/dist/environment/stack-inventory-report.js +78 -0
  218. package/dist/environment/stack-inventory.d.ts +94 -0
  219. package/dist/environment/stack-inventory.js +418 -0
  220. package/dist/environment/state.d.ts +410 -0
  221. package/dist/environment/state.js +2153 -0
  222. package/dist/environment/templates.d.ts +140 -0
  223. package/dist/environment/templates.js +685 -0
  224. package/dist/environment/tool-registration.d.ts +9 -0
  225. package/dist/environment/tool-registration.js +92 -0
  226. package/dist/environment/upgrade-application.d.ts +19 -0
  227. package/dist/environment/upgrade-application.js +175 -0
  228. package/dist/environment-exec/execute.d.ts +105 -0
  229. package/dist/environment-exec/execute.js +1179 -0
  230. package/dist/environment-exec/libexec-program.d.ts +6 -0
  231. package/dist/environment-exec/libexec-program.js +22 -0
  232. package/dist/environment-exec/verified-digest.d.ts +4 -0
  233. package/dist/environment-exec/verified-digest.js +16 -0
  234. package/dist/exact-keys.d.ts +7 -0
  235. package/dist/exact-keys.js +15 -0
  236. package/dist/fix-catalogue.d.ts +32 -0
  237. package/dist/fix-catalogue.js +60 -0
  238. package/dist/freshness.d.ts +29 -0
  239. package/dist/freshness.js +75 -0
  240. package/dist/generated-environment/adopt.d.ts +16 -0
  241. package/dist/generated-environment/adopt.js +271 -0
  242. package/dist/generated-environment/command.d.ts +47 -0
  243. package/dist/generated-environment/command.js +75 -0
  244. package/dist/generated-environment/transaction.d.ts +186 -0
  245. package/dist/generated-environment/transaction.js +1770 -0
  246. package/dist/grading/evaluate.d.ts +71 -0
  247. package/dist/grading/evaluate.js +403 -0
  248. package/dist/grading/grade.d.ts +33 -0
  249. package/dist/grading/grade.js +57 -0
  250. package/dist/guards/answer-confirmation.d.ts +41 -0
  251. package/dist/guards/answer-confirmation.js +109 -0
  252. package/dist/guards/evaluator.d.ts +130 -0
  253. package/dist/guards/evaluator.js +1258 -0
  254. package/dist/guards/harness-adapter.d.ts +17 -0
  255. package/dist/guards/harness-adapter.js +57 -0
  256. package/dist/guards/host.d.ts +8 -0
  257. package/dist/guards/host.js +10 -0
  258. package/dist/guards/invocation-parser.d.ts +66 -0
  259. package/dist/guards/invocation-parser.js +595 -0
  260. package/dist/guards/local-identity.d.ts +44 -0
  261. package/dist/guards/local-identity.js +129 -0
  262. package/dist/guards/policy-change.d.ts +43 -0
  263. package/dist/guards/policy-change.js +159 -0
  264. package/dist/guards/policy-pin.d.ts +13 -0
  265. package/dist/guards/policy-pin.js +177 -0
  266. package/dist/guards/protected-locations.d.ts +62 -0
  267. package/dist/guards/protected-locations.js +461 -0
  268. package/dist/guards/read-only.d.ts +37 -0
  269. package/dist/guards/read-only.js +315 -0
  270. package/dist/init/command.d.ts +41 -0
  271. package/dist/init/command.js +330 -0
  272. package/dist/init/derive.d.ts +53 -0
  273. package/dist/init/derive.js +819 -0
  274. package/dist/init/draft.d.ts +140 -0
  275. package/dist/init/draft.js +1228 -0
  276. package/dist/init/render.d.ts +47 -0
  277. package/dist/init/render.js +104 -0
  278. package/dist/init/scaffold.d.ts +53 -0
  279. package/dist/init/scaffold.js +121 -0
  280. package/dist/invocation.d.ts +13 -0
  281. package/dist/invocation.js +20 -0
  282. package/dist/package-metadata.d.ts +9 -0
  283. package/dist/package-metadata.js +19 -0
  284. package/dist/readiness.d.ts +53 -0
  285. package/dist/readiness.js +88 -0
  286. package/dist/references/named.d.ts +24 -0
  287. package/dist/references/named.js +27 -0
  288. package/dist/refusal.d.ts +15 -0
  289. package/dist/refusal.js +7 -0
  290. package/dist/registry-derivation.d.ts +31 -0
  291. package/dist/registry-derivation.js +32 -0
  292. package/dist/registry-resolve/command.d.ts +47 -0
  293. package/dist/registry-resolve/command.js +226 -0
  294. package/dist/registry-resolve/registry.d.ts +68 -0
  295. package/dist/registry-resolve/registry.js +292 -0
  296. package/dist/registry-resolve/render-shell.d.ts +18 -0
  297. package/dist/registry-resolve/render-shell.js +36 -0
  298. package/dist/repair.d.ts +29 -0
  299. package/dist/repair.js +8 -0
  300. package/dist/repository-path.d.ts +9 -0
  301. package/dist/repository-path.js +16 -0
  302. package/dist/schema-json.d.ts +1 -0
  303. package/dist/schema-json.js +177 -0
  304. package/dist/schema.d.ts +1289 -0
  305. package/dist/schema.js +985 -0
  306. package/dist/setup/command.d.ts +4 -0
  307. package/dist/setup/command.js +4 -0
  308. package/dist/sha256.d.ts +1 -0
  309. package/dist/sha256.js +4 -0
  310. package/dist/surfaces/adoption.d.ts +41 -0
  311. package/dist/surfaces/adoption.js +217 -0
  312. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  313. package/dist/surfaces/consumer-instructions.js +43 -0
  314. package/dist/surfaces/contract.d.ts +42 -0
  315. package/dist/surfaces/contract.js +169 -0
  316. package/dist/surfaces/core-declarations.d.ts +24 -0
  317. package/dist/surfaces/core-declarations.js +79 -0
  318. package/dist/surfaces/development-markers.d.ts +1 -0
  319. package/dist/surfaces/development-markers.js +18 -0
  320. package/dist/surfaces/generator-adapter.d.ts +71 -0
  321. package/dist/surfaces/generator-adapter.js +809 -0
  322. package/dist/surfaces/harness-registry.d.ts +260 -0
  323. package/dist/surfaces/harness-registry.js +146 -0
  324. package/dist/surfaces/mcp.d.ts +145 -0
  325. package/dist/surfaces/mcp.js +349 -0
  326. package/dist/surfaces/merge.d.ts +30 -0
  327. package/dist/surfaces/merge.js +105 -0
  328. package/dist/surfaces/ownership.d.ts +190 -0
  329. package/dist/surfaces/ownership.js +795 -0
  330. package/dist/surfaces/projection.d.ts +136 -0
  331. package/dist/surfaces/projection.js +716 -0
  332. package/dist/surfaces/render.d.ts +79 -0
  333. package/dist/surfaces/render.js +636 -0
  334. package/dist/surfaces/restrictions.d.ts +130 -0
  335. package/dist/surfaces/restrictions.js +447 -0
  336. package/dist/surfaces/selftest.d.ts +162 -0
  337. package/dist/surfaces/selftest.js +393 -0
  338. package/dist/surfaces/skill-contract.d.ts +85 -0
  339. package/dist/surfaces/skill-contract.js +449 -0
  340. package/dist/surfaces/sync.d.ts +57 -0
  341. package/dist/surfaces/sync.js +164 -0
  342. package/dist/team-metadata.d.ts +41 -0
  343. package/dist/team-metadata.js +513 -0
  344. package/dist/testing/gates.d.ts +91 -0
  345. package/dist/testing/gates.js +182 -0
  346. package/dist/testing/hooks.d.ts +59 -0
  347. package/dist/testing/hooks.js +139 -0
  348. package/dist/testing/test-protection.d.ts +34 -0
  349. package/dist/testing/test-protection.js +137 -0
  350. package/dist/testing/verdict.d.ts +29 -0
  351. package/dist/testing/verdict.js +62 -0
  352. package/dist/testing/weakening.d.ts +15 -0
  353. package/dist/testing/weakening.js +30 -0
  354. package/dist/work-orders/application-closure.d.ts +7 -0
  355. package/dist/work-orders/application-closure.js +76 -0
  356. package/dist/work-orders/application-context.d.ts +162 -0
  357. package/dist/work-orders/application-context.js +137 -0
  358. package/dist/work-orders/application-decisions.d.ts +27 -0
  359. package/dist/work-orders/application-decisions.js +121 -0
  360. package/dist/work-orders/application-grading.d.ts +11 -0
  361. package/dist/work-orders/application-grading.js +212 -0
  362. package/dist/work-orders/application.d.ts +6 -0
  363. package/dist/work-orders/application.js +223 -0
  364. package/dist/work-orders/composition.d.ts +101 -0
  365. package/dist/work-orders/composition.js +1202 -0
  366. package/dist/work-orders/contract.d.ts +77 -0
  367. package/dist/work-orders/contract.js +140 -0
  368. package/dist/work-orders/decisions.d.ts +23 -0
  369. package/dist/work-orders/decisions.js +55 -0
  370. package/dist/work-orders/store.d.ts +207 -0
  371. package/dist/work-orders/store.js +291 -0
  372. package/docs/README.md +68 -0
  373. package/docs/architecture.md +136 -0
  374. package/docs/capability-matrix.md +58 -0
  375. package/docs/cli-reference.md +807 -0
  376. package/docs/command-list.md +62 -0
  377. package/docs/compose-patterns.md +99 -0
  378. package/docs/concepts.md +173 -0
  379. package/docs/configuration-essentials.md +177 -0
  380. package/docs/configuration-reference.md +636 -0
  381. package/docs/docs-check.md +76 -0
  382. package/docs/environment.md +948 -0
  383. package/docs/extending.md +43 -0
  384. package/docs/faq.md +113 -0
  385. package/docs/getting-started.md +692 -0
  386. package/docs/harness-surfaces.md +500 -0
  387. package/docs/joining-a-team.md +76 -0
  388. package/docs/release-policy.md +49 -0
  389. package/docs/safety-and-autonomy.md +463 -0
  390. package/docs/troubleshooting.md +382 -0
  391. package/docs/tutorial.md +171 -0
  392. package/docs/work-and-browser.md +420 -0
  393. package/libexec/browser-operation.cjs +270 -0
  394. package/libexec/fence-marker.cjs +101 -0
  395. package/libexec/fence-marker.d.cts +24 -0
  396. package/libexec/fenced-group-runner.cjs +77 -0
  397. package/libexec/fenced-supervisor.cjs +137 -0
  398. package/libexec/generated-filesystem.cjs +731 -0
  399. package/libexec/install-filesystem.cjs +225 -0
  400. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  401. package/libexec/rulesync-generator.cjs +743 -0
  402. package/libexec/surface-generator-worker.cjs +266 -0
  403. package/libexec/surface-output-reader.cjs +99 -0
  404. package/libexec/surface-scratch-cleanup.cjs +14 -0
  405. package/libexec/validate-harness-evidence.d.mts +183 -0
  406. package/libexec/validate-harness-evidence.mjs +1312 -0
  407. package/package.json +74 -4
  408. package/surfaces/README.md +33 -0
  409. package/surfaces/agents/evaluator.agent.md +76 -0
  410. package/surfaces/guards/bash-guard.sh +48 -0
  411. package/surfaces/guards/claude.adapter.json +25 -0
  412. package/surfaces/guards/codex.adapter.json +24 -0
  413. package/surfaces/guards/opencode.adapter.json +25 -0
  414. package/surfaces/references/browser.md +63 -0
  415. package/surfaces/references/test-data-and-personas.md +84 -0
  416. package/surfaces/references/testing-tiers.md +11 -0
  417. package/surfaces/skills/cockpit-browser.skill.md +59 -0
  418. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  419. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  420. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  421. package/surfaces/skills/cockpit-work.skill.md +34 -0
  422. package/surfaces/skills/collection.json +5 -0
  423. package/surfaces/skills/procedures/explain.md +66 -0
  424. package/surfaces/skills/procedures/fix.md +95 -0
  425. package/surfaces/skills/procedures/setup.md +452 -0
  426. package/surfaces/skills/procedures/work.md +134 -0
@@ -0,0 +1,677 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstatIfPresentSync } from '../presence.js';
3
+ import { lstatSync, realpathSync } from 'node:fs';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { executeProbe } from '../../environment-exec/execute.js';
8
+ import { hasErrorCode, RefusalError, ENVIRONMENT_CODES, errorCause, snapshotJson, } from '../contract.js';
9
+ import { GUARD_EXECUTABLE_SEARCH_PATH } from '../executable.js';
10
+ import { CockpitConfig } from '../../schema.js';
11
+ import { pinnedPolicyDenial } from '../../guards/policy-pin.js';
12
+ import { guardConfigChange, guardConfigHeldReason } from '../../guards/policy-change.js';
13
+ import { evaluateGuard, foldGuardSpelling, GUARD_LOCATION_LIMITS, GUARD_ROOT_PROTECTIONS, guardIdentity, } from '../../guards/evaluator.js';
14
+ import { commandSpelling, ordinarySessionCommand, resolveOrdinaryInvocation, UNSYNCED_ADMITTED_COMMANDS, UNSYNCED_ADMITTED_PROSE } from '../../command-table.js';
15
+ import { effectivePolicy } from '../../autonomy.js';
16
+ import { GUARD_INPUT_LIMITS, parseGuardInput } from '../../guards/invocation-parser.js';
17
+ import { readAnchoredFile, readAnchoredBatches, nodeAuthority } from '../../generated-environment/transaction.js';
18
+ import { parseOwnershipManifest } from '../../surfaces/ownership.js';
19
+ import { installedPackageMetadata } from '../../package-metadata.js';
20
+ import { ANSWER_CONFIRMATION_PATH } from '../../guards/answer-confirmation.js';
21
+ import { GUARD_LOCAL_PATHS, parseGuardLocalIdentity } from '../../guards/local-identity.js';
22
+ import { guardInstalledDirectories, guardLauncherEvidence, guardSearchDirectories, guardSearchPathRelativeEntry, guardSecretFiles, guardSensitivePathsBelow, guardTrackedSensitivePaths, guardUnignoredSensitivePaths, } from '../../guards/protected-locations.js';
23
+ import { classifyReadOnlyInvocation, cockpitLauncherPrefix } from '../../guards/read-only.js';
24
+ import { fileFingerprint, recordVerifiedDigest } from '../../environment-exec/verified-digest.js';
25
+ import { isGuardSurfaceTarget } from '../../surfaces/harness-registry.js';
26
+ import { cockpitCommand, formatShellCommand } from '../../fix-catalogue.js';
27
+ import { observeComposeTopology } from '../compose-topology.js';
28
+ import { declaredRuntime } from '../declaration.js';
29
+ import { observeInstallPolicyLockfiles, withInstallPolicyProjection } from '../install-policy-observation.js';
30
+ import { resolveInstallPolicies, renderGuardProjectCommand } from '../install-policy.js';
31
+ import { checkoutId as deriveCheckoutId, publicationCockpitId } from '../identity.js';
32
+ import { consumeSetupCheckoutProjection, observeSetupCheckout, withSetupCheckoutProjection, } from '../setup-observation.js';
33
+ import { browserPreapprovalRequested, loadCockpitConfig, consumeLoadedConfigProjection, STRUCTURED_INPUT_BYTE_LIMIT } from '../../config/load.js';
34
+ import { deriveRegistry } from '../../registry-derivation.js';
35
+ import { confinedPath, lifecycleRefusal, ownDataValues, sameSource } from './shared.js';
36
+ import { readSiblingCheckouts, servedSiblings } from '../sibling-checkouts.js';
37
+ // `npx --no cockpit logs --service <id>` is an ordinary invocation only for a declared service. A
38
+ // cockpit's services are whatever its own Compose files resolve to, so they are read from Compose
39
+ // rather than guessed, and an unreadable topology refuses.
40
+ async function guardServiceIds(config, root, observe) {
41
+ const runtime = declaredRuntime(config.environment);
42
+ if (runtime === 'none')
43
+ return [];
44
+ const docker = observe.resolveDocker ? await observe.resolveDocker() : undefined;
45
+ if (docker?.state !== 'available')
46
+ return docker?.reason ?? 'no Docker executable resolver is bound';
47
+ const topology = await observeComposeTopology({ root, references: config.environment.compose_files,
48
+ projectName: config.cockpit.name, docker: docker.value, node: observe.node, env: { PATH: GUARD_EXECUTABLE_SEARCH_PATH } }, observe.execute);
49
+ return topology.kind === 'complete' ? topology.services.map(service => service.id) : topology.reason;
50
+ }
51
+ function guardHeld(identity, what, why, fix) {
52
+ const sync = `\`${cockpitCommand('sync', { config: identity.configPath })}\` in a terminal, outside the agent`;
53
+ const action = fix.kind === 'sync' ? `a human must run ${sync}, then commit any regenerated files`
54
+ : `a human must restore it with \`${formatShellCommand(['git', '-C', identity.root, 'restore', '--', fix.path])}\` (it is committed), or run ${sync}`;
55
+ return `${what}; ${why}, so every tool call is held, including a sync run through this agent. Fix: ${action}.`;
56
+ }
57
+ // An edit that does not widen what agents may do needs only a sync, which the agent may run itself;
58
+ // every other call waits for it, so no call is judged under a policy sync has not published.
59
+ function unsyncedHold(identity, detail) {
60
+ const sync = cockpitCommand('sync', { config: identity.configPath });
61
+ const reason = `${identity.configPath} changed since the guards were generated without widening what agents may do${detail}; every tool call except ${UNSYNCED_ADMITTED_PROSE} is held until it is synced. Fix: run \`${sync}\` (this agent may run it where [autonomy] sync = "agent"), then commit any regenerated files.`;
62
+ return { decision: { decision: 'unavailable', reason }, diagnostics: [{ severity: 'error', target: 'guard', message: reason,
63
+ code: 'GUARD_CONFIG_UNSYNCED',
64
+ fix: `run ${sync}; an agent may run it where [autonomy] sync = "agent"` }] };
65
+ }
66
+ const GUARD_RESTORABLE = new Set(['.cockpit/generated-environment-ownership.json', '.cockpit/guards/bash-guard.sh',
67
+ '.claude/settings.json', '.codex/hooks.json', '.opencode/plugin/cockpit-guards.js']);
68
+ /** Installed bytes are authority only while their source, roots and publication still match. */
69
+ export async function runGuardEvaluationApplication(input, adapters) {
70
+ const unavailable = (reason) => ({
71
+ decision: { decision: 'unavailable', reason },
72
+ diagnostics: [{ severity: 'error', code: 'GUARD_AUTHORITY_UNAVAILABLE', message: reason, target: 'guard' }],
73
+ });
74
+ try {
75
+ const data = ownDataValues(snapshotJson(input), ['cwd', 'guardRoot', 'harness', 'identity', 'payload'], ['askIsDeny', 'ripgrepConfigActive', 'searchPath']);
76
+ if (!data
77
+ || data.searchPath !== undefined && (typeof data.searchPath !== 'string' || /[\x00-\x1f\x7f]/.test(data.searchPath) || Buffer.byteLength(data.searchPath) > GUARD_INPUT_LIMITS.inputBytes)
78
+ || data.askIsDeny !== undefined && typeof data.askIsDeny !== 'boolean'
79
+ || data.ripgrepConfigActive !== undefined && typeof data.ripgrepConfigActive !== 'boolean'
80
+ || !isGuardSurfaceTarget(data.harness))
81
+ return unavailable('Guard request is malformed.');
82
+ const harness = data.harness;
83
+ const captured = ownDataValues(snapshotJson(data.identity), ['root', 'rootDevice', 'rootInode', 'configPath', 'configSha256', 'configDevice', 'configInode', 'node', 'nodeSha256', 'nodeFingerprint', 'module', 'moduleSha256']);
84
+ if (!captured
85
+ || !Object.values(captured).every(value => typeof value === 'string')
86
+ || !['configSha256', 'nodeSha256', 'moduleSha256'].every(key => /^[a-f0-9]{64}$/.test(String(captured[key]))))
87
+ return unavailable('Installed guard identity is missing or malformed.');
88
+ const identity = captured;
89
+ if ([identity.root, identity.configPath, identity.node, identity.module].some(path => !isAbsolute(path) || resolve(path) !== path || /[\x00-\x1f\x7f-\x9f]/.test(path))
90
+ || dirname(identity.configPath) !== identity.root || typeof data.cwd !== 'string' || realpathSync(data.cwd) !== data.cwd
91
+ // A local identity copied from another checkout would evaluate this one under that one's policy.
92
+ || data.guardRoot !== identity.root)
93
+ return unavailable('Installed guard location does not match this application.');
94
+ const configFile = relative(identity.root, identity.configPath);
95
+ const changed = (what) => unavailable(guardHeld(identity, `${what} since the guards were generated`, 'the guard trusts only the cockpit, Node and configuration it was generated for', { kind: 'sync' }));
96
+ if (realpathSync(fileURLToPath(new URL('../../../dist/cli.js', import.meta.url))) !== identity.module)
97
+ return changed('a different cockpit installation is running');
98
+ const roots = new Map();
99
+ for (const [path, directory] of [[identity.root, true], [identity.configPath, false]]) {
100
+ let status;
101
+ try {
102
+ status = lstatSync(path);
103
+ }
104
+ catch (error) {
105
+ if (hasErrorCode(error, 'ENOENT') && !directory)
106
+ return unavailable(guardHeld(identity, `${identity.configPath} is missing`, 'guard policy comes from it', { kind: 'restore', path: configFile }));
107
+ throw error;
108
+ }
109
+ // The configuration is authenticated by its content digest below. Holding on its inode
110
+ // as well refused the identical bytes that `git restore`, `sed -i` and editors that save
111
+ // by rename leave in a new file, until a human ran sync for no change.
112
+ if (status.isSymbolicLink() || (directory ? !status.isDirectory() : !status.isFile())
113
+ || directory && (String(status.dev) !== identity.rootDevice || String(status.ino) !== identity.rootInode)
114
+ || realpathSync(path) !== path) {
115
+ return changed(directory ? `the cockpit checkout ${path} was moved or replaced` : `${path} was replaced by a link or a non-file`);
116
+ }
117
+ roots.set(path, status);
118
+ }
119
+ // Re-hashing a 100 MB Node binary in every guard process dominated each call. The local record
120
+ // carries the fingerprint sync hashed; an identical device, inode, size, mode and timestamps
121
+ // stand in for the re-hash, and the local record is checked against this identity below.
122
+ const nodePath = realpathSync(process.execPath);
123
+ if (nodePath === identity.node && fileFingerprint(nodePath, lstatSync(nodePath)) === identity.nodeFingerprint) {
124
+ recordVerifiedDigest(identity.nodeFingerprint, identity.nodeSha256);
125
+ }
126
+ const node = await nodeAuthority();
127
+ if (!node || node.canonicalPath !== identity.node || node.sha256 !== identity.nodeSha256)
128
+ return changed(`the Node executable changed (now ${node?.canonicalPath ?? 'unreadable'})`);
129
+ const read = async (root, path, maxBytes) => {
130
+ const result = await readAnchoredFile(root, path, maxBytes, node, { execute: adapters.execute ?? executeProbe });
131
+ if (result.kind !== 'complete')
132
+ throw new RefusalError('Guard authority file is missing, changed, oversized or crosses a symlink boundary.');
133
+ return result;
134
+ };
135
+ const manifestPath = '.cockpit/generated-environment-ownership.json';
136
+ const registration = { claudecode: '.claude/settings.json', codexcli: '.codex/hooks.json', opencode: '.opencode/plugin/cockpit-guards.js' }[harness];
137
+ // The configuration rides in the same batch: the policy pin applies an edit to exactly these bytes.
138
+ const configName = basename(identity.configPath);
139
+ const publicationRequests = [manifestPath, '.cockpit/guards/bash-guard.sh', registration, GUARD_LOCAL_PATHS.identity, configName].map(path => ({
140
+ path, action: 'read-file', maxBytes: path === manifestPath || path === configName ? 1_048_576 : 65_536,
141
+ }));
142
+ const readPublication = async () => {
143
+ const batch = await readAnchoredBatches(identity.root, publicationRequests, node, { execute: adapters.execute ?? executeProbe });
144
+ if (batch?.kind !== 'complete' || batch.results.length !== publicationRequests.length)
145
+ throw new RefusalError('Guard publication read could not be authenticated.');
146
+ return new Map(publicationRequests.map((request, index) => {
147
+ const result = batch.results[index];
148
+ if (result?.kind === 'missing')
149
+ throw new GuardFileMissing(request.path);
150
+ if (result?.kind !== 'complete')
151
+ throw new RefusalError('Guard authority file is missing, changed, oversized or crosses a symlink boundary.');
152
+ return [request.path, result];
153
+ }));
154
+ };
155
+ // The module, configuration and publication reads are independent and each costs a child
156
+ // process, so they run together. Results are consumed in the order the checks below need, so
157
+ // the first failing check is the one reported.
158
+ const settle = (work) => work.then(value => ({ value }), (error) => ({ error }));
159
+ const unwrap = (settled) => { if ('error' in settled)
160
+ throw settled.error; return settled.value; };
161
+ const modulePending = settle(read(dirname(identity.module), basename(identity.module), 16_777_216));
162
+ const configPending = settle(adapters.loadConfig(identity.configPath));
163
+ const publicationPending = settle(readPublication());
164
+ const checkoutsPending = settle(readSiblingCheckouts(identity.root));
165
+ const module = unwrap(await modulePending);
166
+ if (module.sha256 !== identity.moduleSha256)
167
+ return changed('the installed cockpit version changed (its handler bytes differ)');
168
+ const loaded = unwrap(await configPending);
169
+ if (loaded.kind !== 'complete' || loaded.result.source.path !== identity.configPath) {
170
+ return unavailable(guardHeld(identity, `${identity.configPath} changed since the guards were generated`, 'guard policy comes from it and an edit takes effect only once a human approves it', { kind: 'sync' }));
171
+ }
172
+ // Every check below runs against the configuration on disk; an edit since sync is admitted
173
+ // only when it does not widen the one the guards were generated from, and then only for sync.
174
+ const configSha256 = loaded.result.source.sha256;
175
+ const unsynced = configSha256 !== identity.configSha256;
176
+ if (unsynced) {
177
+ const local = (path) => readAnchoredFile(identity.root, path, STRUCTURED_INPUT_BYTE_LIMIT, node, { execute: adapters.execute ?? executeProbe });
178
+ const [recorded, confirmations] = await Promise.all([local(GUARD_LOCAL_PATHS.generatedConfig), local(ANSWER_CONFIRMATION_PATH)]);
179
+ const change = guardConfigChange(identity.configSha256, recorded.kind === 'complete' ? recorded.bytes : undefined, { sha256: configSha256, config: loaded.result.config }, confirmations.kind === 'complete' ? confirmations.bytes : undefined);
180
+ if (change.kind === 'widening' || change.kind === 'unrecorded')
181
+ return unavailable(guardHeld(identity, `${identity.configPath} changed since the guards were generated and ${guardConfigHeldReason(change)}`, 'guard policy comes from it and an edit that may widen it takes effect only once a human approves it', { kind: 'sync' }));
182
+ }
183
+ const config = loaded.result.config;
184
+ let publication;
185
+ try {
186
+ publication = unwrap(await publicationPending);
187
+ }
188
+ catch (error) {
189
+ if (!(error instanceof GuardFileMissing))
190
+ throw error;
191
+ return unavailable(GUARD_RESTORABLE.has(error.path)
192
+ ? guardHeld(identity, `${error.path} is missing`, 'it authenticates the guard and the hook that launches it', { kind: 'restore', path: error.path })
193
+ : guardHeld(identity, `${error.path} is missing`, 'it records the Node executable and cockpit handler on this machine, which committed files cannot carry', { kind: 'sync' }));
194
+ }
195
+ const local = parseGuardLocalIdentity(publication.get(GUARD_LOCAL_PATHS.identity).bytes.toString('utf8'));
196
+ if (!local || !isDeepStrictEqual(local, identity))
197
+ return unavailable('Installed guard identity does not match its machine-local record.');
198
+ const manifestRead = publication.get(manifestPath);
199
+ const ownership = parseOwnershipManifest(manifestRead.bytes.toString('utf8'), {
200
+ root: identity.root, configPath: identity.configPath,
201
+ cockpitId: publicationCockpitId({ name: config.cockpit.name }),
202
+ });
203
+ if (ownership.kind !== 'complete' || ownership.manifest.source.sha256 !== identity.configSha256)
204
+ return unavailable('Installed guard publication is stale or unauthenticated.');
205
+ const producer = ownership.manifest.producer;
206
+ if (producer.package !== installedPackageMetadata.name
207
+ || producer.version !== installedPackageMetadata.version
208
+ || producer.sourceRevision !== installedPackageMetadata.cockpitSourceRevision)
209
+ return changed(`the installed cockpit version changed (the guards were generated by ${producer.package} ${producer.version}, ${installedPackageMetadata.name} ${installedPackageMetadata.version} is installed)`);
210
+ const published = new Map();
211
+ for (const path of ['.cockpit/guards/bash-guard.sh', registration]) {
212
+ const entry = ownership.manifest.entries.find(entry => entry.path === path);
213
+ const actual = publication.get(path);
214
+ if (!entry || entry.kind !== 'file' || entry.sha256 !== actual.sha256)
215
+ return unavailable('Installed guard registration or launcher bytes drifted.');
216
+ published.set(path, actual.sha256);
217
+ }
218
+ const git = await adapters.resolveGit();
219
+ if (git.state !== 'available')
220
+ return unavailable('Guard checkout observation requires an authenticated Git executable.');
221
+ const checkout = await adapters.observeCheckout({ source: loaded.result.source, cockpitName: config.cockpit.name,
222
+ observedAt: new Date().toISOString(), env: {}, gitExecutable: git.value });
223
+ const parsed = parseGuardInput(data.payload);
224
+ if (parsed.status !== 'parsed')
225
+ return unavailable(parsed.reason);
226
+ if (parsed.cwd !== data.cwd)
227
+ return unavailable('Guard request cwd does not match its invocation authority.');
228
+ const prepared = await prepareGuardEvaluationPolicy({ configuration: loaded.result, checkout }, checkoutsPending.then(unwrap));
229
+ if (prepared.kind !== 'complete')
230
+ return unsynced
231
+ ? unsyncedHold(identity, `, but the guard cannot build its policy from it (${prepared.diagnostics[0]?.message ?? 'no reason given'})`)
232
+ : unavailable('Guard policy authority could not be authenticated.');
233
+ // The inspector answers synchronously, so what only Git can say is asked first: which protected
234
+ // paths a default ripgrep search reads, and which a repository's index holds for a Git command
235
+ // that prints committed content. A request not asked here gets the walk, or no answer.
236
+ const policy = prepared.result.policy;
237
+ const instancePaths = policy.instances.map(item => item.path);
238
+ const present = (path) => { try {
239
+ lstatSync(path);
240
+ return true;
241
+ }
242
+ catch (error) {
243
+ if (error.code === 'ENOENT')
244
+ return false;
245
+ throw error;
246
+ } };
247
+ const allowed = (path) => policy.allowedRoots.some(root => confinedPath(root, path));
248
+ // Each entry starts as the answer for Git not answering and is replaced by Git's.
249
+ const unignored = new Map();
250
+ const tracked = new Map();
251
+ const queries = [];
252
+ for (const invocation of parsed.invocations) {
253
+ const access = classifyReadOnlyInvocation(invocation, { ripgrepConfigActive: data.ripgrepConfigActive === true });
254
+ if (access?.trackedContent && allowed(access.cwd) && !tracked.has(access.cwd)) {
255
+ tracked.set(access.cwd, null);
256
+ queries.push(async () => { tracked.set(access.cwd, await guardTrackedSensitivePaths(access.cwd, instancePaths, git.value, adapters.execute ?? executeProbe)); });
257
+ }
258
+ for (const tree of access?.skipsIgnored ? access.trees : []) {
259
+ const path = resolve(access.cwd, tree);
260
+ if (!allowed(path) || unignored.has(path) || !present(path))
261
+ continue;
262
+ unignored.set(path, undefined);
263
+ queries.push(async () => { unignored.set(path, await guardUnignoredSensitivePaths(path, instancePaths, git.value, adapters.execute ?? executeProbe)); });
264
+ }
265
+ }
266
+ // A query starts up to four Git processes, so a command naming many trees is asked about a few
267
+ // at a time, and a failed one stops the rest from starting.
268
+ const worker = async () => {
269
+ for (let query = queries.shift(); query; query = queries.shift())
270
+ await query().catch((error) => { queries.length = 0; throw error; });
271
+ };
272
+ await Promise.all([worker(), worker(), worker(), worker()]);
273
+ const walks = new Map();
274
+ const searched = (path, unignoredOnly) => {
275
+ const listed = unignoredOnly ? unignored.get(path) : undefined;
276
+ if (listed !== undefined)
277
+ return listed;
278
+ let walked = walks.get(path);
279
+ if (walked === undefined)
280
+ walks.set(path, walked = guardSensitivePathsBelow(path));
281
+ // The walk names environment and secrets names; an ignore-aware search also needs the instances it reads.
282
+ if (!unignoredOnly || walked === null)
283
+ return walked;
284
+ const found = [...new Set([...walked, ...instancePaths.filter(item => confinedPath(path, item) && present(item))])].sort();
285
+ return found.length > GUARD_LOCATION_LIMITS.sensitivePaths ? null : found;
286
+ };
287
+ const inspected = new Map();
288
+ // The system resolves `..` from where a link leads, so `link/../x` names a sibling of the link's
289
+ // target, not of the link: a spelling that leaves a link through `..` is not its lexical path.
290
+ const leavesLink = (cwd, spelled) => {
291
+ let cursor = isAbsolute(spelled) ? '/' : cwd;
292
+ for (const component of spelled.split('/')) {
293
+ if (component === '..') {
294
+ try {
295
+ const status = lstatSync(cursor);
296
+ if (status.isSymbolicLink())
297
+ return true;
298
+ inspected.set(cursor, status);
299
+ }
300
+ catch (error) {
301
+ // A missing or non-directory component fails the system's lookup before `..` applies.
302
+ if (!['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
303
+ throw error;
304
+ }
305
+ cursor = dirname(cursor);
306
+ }
307
+ else if (component && component !== '.')
308
+ cursor = join(cursor, component);
309
+ }
310
+ return false;
311
+ };
312
+ const inspect = request => {
313
+ const path = resolve(request.cwd, request.path);
314
+ const root = prepared.result.policy.allowedRoots.filter(root => confinedPath(root, path)).sort((a, b) => b.length - a.length)[0];
315
+ if (!root)
316
+ return { status: 'unavailable', path, symlink: false };
317
+ if (leavesLink(request.cwd, request.path))
318
+ return { status: 'resolved', path, symlink: true };
319
+ let cursor = root;
320
+ const rootStatus = lstatSync(root);
321
+ inspected.set(root, rootStatus);
322
+ // Identities let the evaluator recognise a protected entry under any spelling the volume
323
+ // resolves to it; a missing leaf reports its nearest existing ancestors only.
324
+ const chain = [guardIdentity(rootStatus)];
325
+ let exists = true;
326
+ for (const component of relative(root, path).split(sep).filter(Boolean)) {
327
+ cursor = join(cursor, component);
328
+ let status;
329
+ try {
330
+ status = lstatSync(cursor);
331
+ }
332
+ catch (error) {
333
+ if (error.code === 'ENOENT') {
334
+ exists = false;
335
+ break;
336
+ }
337
+ throw error;
338
+ }
339
+ if (status.isSymbolicLink())
340
+ return { status: 'resolved', path, symlink: true };
341
+ inspected.set(cursor, status);
342
+ chain.push(guardIdentity(status));
343
+ }
344
+ const search = request.operation === 'search' || request.operation === 'unignored-search';
345
+ return { status: 'resolved', path, symlink: false, identity: exists ? chain.at(-1) : null, ancestors: exists ? chain.slice(0, -1) : chain,
346
+ ...(search ? { sensitive: exists ? searched(path, request.operation === 'unignored-search') : [] } : {}),
347
+ ...(request.operation === 'repository' ? { tracked: tracked.get(path) ?? null } : {}) };
348
+ };
349
+ const cockpitInvocations = [];
350
+ const launcher = resolve(dirname(identity.module), '../bin/cockpit.mjs');
351
+ const searchPath = data.searchPath ?? '';
352
+ const launched = [];
353
+ const prove = (executable, cwd, argv, session) => {
354
+ const prefix = executable === null ? undefined : cockpitLauncherPrefix(executable, argv);
355
+ const evidence = prefix === undefined ? undefined : guardLauncherEvidence(executable, argv.slice(0, prefix), cwd, identity.root, launcher, searchPath);
356
+ if (evidence === undefined)
357
+ return undefined;
358
+ inspected.set(evidence, lstatSync(evidence));
359
+ const proven = { executable: executable, cwd, argv, prefix: prefix, session };
360
+ launched.push(proven);
361
+ return proven;
362
+ };
363
+ const configured = { canonicalRoot: identity.root, configPath: identity.configPath, profiles: agentProfiles(config) };
364
+ for (const { executable, cwd, argv } of parsed.invocations) {
365
+ const proven = prove(executable, cwd, argv, true);
366
+ // The evaluator judges a session's command as one more invocation, so its spelling needs proof too.
367
+ const inner = proven && ordinarySessionCommand(argv.slice(proven.prefix), cwd, configured);
368
+ if (inner)
369
+ prove(inner[0], cwd, inner.slice(1), false);
370
+ }
371
+ if (unsynced && !(parsed.tool === 'Bash' && parsed.invocations.length > 0 && parsed.invocations.every(invocation => launched.some(entry => entry.session && entry.executable === invocation.executable && entry.cwd === invocation.cwd
372
+ && isDeepStrictEqual(entry.argv, invocation.argv) && UNSYNCED_ADMITTED_COMMANDS.some(name => name === commandSpelling(entry.argv.slice(entry.prefix)).name)))))
373
+ return unsyncedHold(identity, '');
374
+ let launcherSha256;
375
+ if (launched.length) {
376
+ const status = lstatSync(launcher);
377
+ if (!status.isFile() || status.isSymbolicLink() || !(status.mode & 0o111) || realpathSync(launcher) !== launcher) {
378
+ return unavailable('Guard ordinary command executable is unavailable or ambiguous.');
379
+ }
380
+ launcherSha256 = (await read(dirname(launcher), basename(launcher), 65_536)).sha256;
381
+ inspected.set(launcher, status);
382
+ const services = await guardServiceIds(config, identity.root, { node, resolveDocker: adapters.resolveDocker, execute: adapters.execute ?? executeProbe });
383
+ if (typeof services === 'string')
384
+ return unavailable(`Guard could not read the Compose service list: ${services}`);
385
+ const authority = { executable: launcher, ...configured, allowedRoots: prepared.result.policy.allowedRoots, services,
386
+ gates: config.testing?.gates.map(gate => gate.id) ?? [], grants: ordinaryGrants(config) };
387
+ for (const { executable, cwd, argv, prefix, session } of launched) {
388
+ const args = argv.slice(prefix);
389
+ // A session inside a session would run a command the evaluator never sees.
390
+ const admitted = session && ordinarySessionCommand(args, cwd, configured) ? {}
391
+ : resolveOrdinaryInvocation({ executable: launcher, cwd, argv: args }, authority, path => {
392
+ const fact = inspect({ path, cwd, operation: 'write' });
393
+ return !!fact && typeof fact === 'object' && 'status' in fact && fact.status === 'resolved'
394
+ && 'symlink' in fact && fact.symlink === false;
395
+ });
396
+ // The evaluator matches the invocation as the agent spelled it, and checks what it reads.
397
+ if (admitted)
398
+ cockpitInvocations.push({ executable, cwd, argv, ...(admitted.reads ? { reads: admitted.reads } : {}) });
399
+ }
400
+ }
401
+ if (cockpitInvocations.length + prepared.result.policy.projectCommands.length > GUARD_INPUT_LIMITS.invocations)
402
+ return unavailable('Guard candidates exceed the invocation bound.');
403
+ // The policy pin needs the text an edit applies to, which must be the text the policy was built from.
404
+ const configRead = publication.get(configName);
405
+ if (configRead.sha256 !== configSha256)
406
+ return unavailable('Guard configuration changed during evaluation.');
407
+ const configStatus = lstatSync(identity.configPath);
408
+ // A hard link or another spelling of the configuration is the configuration.
409
+ const isConfig = (path) => {
410
+ if (foldGuardSpelling(path) === foldGuardSpelling(identity.configPath))
411
+ return true;
412
+ const status = lstatIfPresentSync(path);
413
+ return status !== undefined && status.dev === configStatus.dev && status.ino === configStatus.ino;
414
+ };
415
+ const pinned = pinnedPolicyDenial(parsed, configRead.bytes.toString('utf8'), isConfig);
416
+ if (pinned)
417
+ return { decision: pinned, diagnostics: [{ severity: 'error', code: 'GUARD_REFUSED', message: pinned.reason, target: 'guard' }] };
418
+ const decision = evaluateGuard(parsed, { ...prepared.result.policy, cockpitInvocations,
419
+ cockpitLaunchers: launched.map(({ executable, cwd, argv, prefix }) => ({ executable, cwd, argv: argv.slice(0, prefix) })),
420
+ searchDirectories: guardSearchDirectories(searchPath, prepared.result.policy.allowedRoots),
421
+ searchPathRelativeEntry: guardSearchPathRelativeEntry(searchPath),
422
+ ripgrepConfigActive: data.ripgrepConfigActive === true }, inspect);
423
+ const current = await adapters.loadConfig(identity.configPath);
424
+ if (current.kind !== 'complete' || current.result.source.sha256 !== configSha256)
425
+ return unavailable('Guard configuration changed during evaluation.');
426
+ for (const [path, before] of [...roots, ...inspected]) {
427
+ const after = lstatSync(path);
428
+ // Directories may receive unrelated leaves, but replacement and file changes invalidate evidence.
429
+ if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode
430
+ || before.isFile() && (before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || before.size !== after.size))
431
+ return unavailable('Guard filesystem authority changed during evaluation.');
432
+ }
433
+ const currentPublication = await readPublication();
434
+ for (const [path, hash] of [[manifestPath, manifestRead.sha256], [GUARD_LOCAL_PATHS.identity, publication.get(GUARD_LOCAL_PATHS.identity).sha256], ...published]) {
435
+ if (currentPublication.get(path).sha256 !== hash)
436
+ return unavailable('Guard publication changed during evaluation.');
437
+ }
438
+ if ((await read(dirname(identity.module), basename(identity.module), 16_777_216)).sha256 !== identity.moduleSha256)
439
+ return unavailable('Guard handler changed during evaluation.');
440
+ if (launcherSha256 && (await read(dirname(launcher), basename(launcher), 65_536)).sha256 !== launcherSha256)
441
+ return unavailable('Guard ordinary command executable changed during evaluation.');
442
+ return { decision, diagnostics: decision.decision === 'allow' ? [] : [{ severity: 'error', code: 'GUARD_REFUSED', message: decision.reason, target: 'guard' }] };
443
+ }
444
+ catch (error) {
445
+ return unavailable(`Guard authority could not be read or revalidated: ${errorCause(error)}`);
446
+ }
447
+ }
448
+ // The guard runs against the configuration sync generated it from, or one that does not widen it and
449
+ // admits only sync and check, so the policy read here grants nothing the last sync did not.
450
+ function ordinaryGrants(config) {
451
+ const policy = effectivePolicy(config);
452
+ return [...config.browser && browserPreapprovalRequested(config.browser) ? ['browser-preapproval'] : [],
453
+ ...policy.autonomy.sync === 'agent' ? ['autonomy-sync'] : [], ...policy.autonomy.reset === 'agent' ? ['autonomy-reset'] : []];
454
+ }
455
+ // `up --profile` and `session --profile` start that profile's services for one run. Only the default
456
+ // set a person declared is theirs to name, and a profile that mounts an operator-gated sibling, which
457
+ // touches live data or local hardware, never starts without a person.
458
+ function agentProfiles(config) {
459
+ return config.environment.profiles.filter(profile => !config.sibling.some(row => row.gate === 'operator' && row.mount === `profile:${profile}`));
460
+ }
461
+ /**
462
+ * Every checkout the guard grants for a sibling: each declared directory, then each checkout `use`
463
+ * recorded, which is where the agent works on that sibling. A recorded checkout that is gone grants
464
+ * nothing. Allowed roots, instance protection and project commands all come from this one list.
465
+ */
466
+ function siblingRoots(config, siblingsRoot, served) {
467
+ const declared = config.sibling.map((sibling, index) => {
468
+ const root = resolve(siblingsRoot, sibling.dir);
469
+ if (!confinedPath(siblingsRoot, root))
470
+ throw new RefusalError('guard policy sibling escapes its declared root');
471
+ return { sibling, index, root };
472
+ });
473
+ const recorded = declared.flatMap(({ sibling, index }) => {
474
+ const entry = served.get(sibling.slug);
475
+ if (!entry?.overridden)
476
+ return [];
477
+ if (!lstatIfPresentSync(entry.served)?.isDirectory())
478
+ return [];
479
+ return [{ sibling, index, root: entry.served }];
480
+ });
481
+ return [...declared, ...recorded];
482
+ }
483
+ class GuardFileMissing extends RefusalError {
484
+ path;
485
+ constructor(path) {
486
+ super(`${path} is missing`);
487
+ this.path = path;
488
+ }
489
+ }
490
+ function guardPolicyRefusal(message) {
491
+ return lifecycleRefusal(ENVIRONMENT_CODES.LIFECYCLE_EVIDENCE_INVALID, message, 'guard-policy');
492
+ }
493
+ /**
494
+ * Source projections share authenticated authority; installed consumption is admitted separately.
495
+ * `siblingCheckouts` is the configuration directory's sibling checkout record, when the caller
496
+ * started reading it already; it is read here otherwise.
497
+ */
498
+ export async function prepareGuardEvaluationPolicy(input, siblingCheckouts) {
499
+ const inputData = ownDataValues(input, ['configuration', 'checkout']);
500
+ const data = inputData && Object.hasOwn(inputData, 'configuration') && Object.hasOwn(inputData, 'checkout') ? inputData : undefined;
501
+ if (!data)
502
+ return guardPolicyRefusal('guard policy requires own-data configuration and checkout observations');
503
+ const configuration = data.configuration;
504
+ const exposed = snapshotJson(configuration);
505
+ const loaded = consumeLoadedConfigProjection(configuration);
506
+ if (!loaded || !exposed || !isDeepStrictEqual(exposed, snapshotJson(loaded))) {
507
+ return guardPolicyRefusal('guard policy configuration authority is copied, changed or already consumed');
508
+ }
509
+ const derived = deriveRegistry(loaded.config);
510
+ if (derived.kind !== 'complete')
511
+ return guardPolicyRefusal(`guard policy registry refused (${derived.code}): ${derived.message}`);
512
+ const guardConfig = CockpitConfig.parse(derived.result.config);
513
+ const recorded = await (siblingCheckouts ?? readSiblingCheckouts(dirname(loaded.source.path)));
514
+ if (recorded.kind === 'refusal')
515
+ return guardPolicyRefusal(recorded.diagnostic.message);
516
+ const served = servedSiblings(dirname(loaded.source.path), guardConfig, recorded.checkouts);
517
+ const observed = snapshotJson(data.checkout);
518
+ if (observed?.state !== 'available')
519
+ return guardPolicyRefusal('guard policy checkout observation is unavailable');
520
+ try {
521
+ const identities = new Map();
522
+ let checkoutRoots;
523
+ const result = await withSetupCheckoutProjection(data.checkout, {
524
+ source: loaded.source, cockpitName: loaded.config.cockpit.name, evaluatedAt: Date.parse(observed.observedAt),
525
+ }, (projection) => {
526
+ const checkout = consumeSetupCheckoutProjection(projection);
527
+ if (!checkout || !sameSource(checkout.source, loaded.source)) {
528
+ return guardPolicyRefusal('guard policy checkout does not match configuration authority');
529
+ }
530
+ const config = guardConfig;
531
+ const protectedIdentities = new Map();
532
+ // Records the identity of every existing component so the evaluator can match a protected
533
+ // entry by inode rather than by spelling.
534
+ const protect = (root, path, leaf, label) => {
535
+ const segments = relative(root, path).split(sep);
536
+ const chain = [guardIdentity(identities.get(root))];
537
+ let cursor = root;
538
+ let exists = true;
539
+ for (const [index, segment] of segments.entries()) {
540
+ cursor = join(cursor, segment);
541
+ let status;
542
+ try {
543
+ status = lstatSync(cursor);
544
+ }
545
+ catch (error) {
546
+ if (hasErrorCode(error, 'ENOENT')) {
547
+ exists = false;
548
+ break;
549
+ }
550
+ throw error;
551
+ }
552
+ if (status.isSymbolicLink() || (index < segments.length - 1 || leaf === 'directory' ? !status.isDirectory() : !status.isFile())) {
553
+ throw new RefusalError(`guard policy ${label} path has a symlink or unexpected file type`);
554
+ }
555
+ identities.set(cursor, status);
556
+ chain.push(guardIdentity(status));
557
+ }
558
+ protectedIdentities.set(path, { path, identity: exists ? chain.at(-1) : null,
559
+ ancestors: exists ? chain.slice(0, -1) : chain });
560
+ };
561
+ const directory = (path) => {
562
+ const before = lstatSync(path);
563
+ if (!before.isDirectory() || before.isSymbolicLink() || realpathSync(path) !== path
564
+ || !isAbsolute(path) || path === '/' || /[\x00-\x1f\x7f]/.test(path)) {
565
+ throw new RefusalError('guard policy requires canonical directory authority');
566
+ }
567
+ identities.set(path, before);
568
+ return path;
569
+ };
570
+ const canonicalRoot = directory(dirname(loaded.source.path));
571
+ checkoutRoots = siblingRoots(config, directory(resolve(canonicalRoot, config.cockpit.siblings_root)), served);
572
+ const allowedRoots = new Set([canonicalRoot]);
573
+ const instances = [];
574
+ const templates = [];
575
+ for (const { sibling, root } of checkoutRoots) {
576
+ allowedRoots.add(directory(root));
577
+ for (const file of derived.result.secretPaths.filter(row => row.slug === sibling.slug)) {
578
+ const path = resolve(root, file.path);
579
+ if (!confinedPath(root, path) || path === root)
580
+ throw new RefusalError('guard policy instance escapes its sibling root');
581
+ protect(root, path, 'file', 'instance');
582
+ instances.push({ name: `${sibling.slug}-${createHash('sha256').update(file.path).digest('hex').slice(0, 12)}`, path });
583
+ }
584
+ for (const file of sibling.environment_files ?? []) {
585
+ const path = resolve(root, file.template);
586
+ if (!confinedPath(root, path) || path === root)
587
+ throw new RefusalError('guard policy environment template escapes its sibling root');
588
+ templates.push(path);
589
+ }
590
+ }
591
+ if (allowedRoots.size > 256 || instances.length > 256)
592
+ throw new RefusalError('guard policy exceeds restriction root or instance bounds');
593
+ for (const { path, type } of GUARD_ROOT_PROTECTIONS)
594
+ protect(canonicalRoot, join(canonicalRoot, path), type, 'guard control or secrets');
595
+ // The configuration's own identity lets the evaluator recognise its deletion under any spelling.
596
+ const configPath = loaded.source.path;
597
+ protect(canonicalRoot, configPath, 'file', 'configuration');
598
+ // This module runs from dist/environment, so two levels up is the running cockpit package.
599
+ const executionDirectories = guardInstalledDirectories(resolve(fileURLToPath(new URL('../../..', import.meta.url))), canonicalRoot)
600
+ .filter(path => [...allowedRoots].some(root => confinedPath(root, path)));
601
+ const secretFiles = guardSecretFiles(canonicalRoot);
602
+ const protectedFilesLinked = secretFiles.linked || instances.some(item => (identities.get(item.path)?.nlink ?? 1) > 1);
603
+ for (const [path, before] of identities) {
604
+ const after = lstatSync(path);
605
+ if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode
606
+ || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || before.size !== after.size) {
607
+ throw new RefusalError('guard policy root or instance identity changed during construction');
608
+ }
609
+ }
610
+ const policy = { canonicalRoot, configPath,
611
+ allowedRoots: [...allowedRoots], instances, templates,
612
+ protectedIdentities: [...protectedIdentities.values()],
613
+ protectedBranches: [...config.guards.protected_branches],
614
+ hostedDatabaseHosts: [...config.guards.hosted_db_hosts],
615
+ vendorHosts: [...config.guards.vendor_endpoint_hosts],
616
+ declaredAllows: config.guards.allow.map(rule => ({ ...rule,
617
+ ...(rule.cwd === undefined ? {} : { cwd: resolve(canonicalRoot, rule.cwd) }), argv: [...rule.argv] })),
618
+ declaredDenies: config.guards.deny.map(rule => ({
619
+ match: 'prefix' in rule ? 'prefix' : 'contains',
620
+ argv: [...('prefix' in rule ? rule.prefix : rule.contains)], reason: rule.reason,
621
+ ...(rule.fix === undefined ? {} : { fix: rule.fix }),
622
+ })),
623
+ ripgrepConfigActive: false,
624
+ executionDirectories, searchDirectories: [], searchPathRelativeEntry: null,
625
+ secretFileIdentities: secretFiles.identities && [...secretFiles.identities], protectedFilesLinked,
626
+ cockpitInvocations: [], cockpitLaunchers: [], projectCommands: [], };
627
+ return { kind: 'complete', status: 0, diagnostics: [],
628
+ result: { policy } };
629
+ });
630
+ if (!result || result.kind !== 'complete' || !checkoutRoots) {
631
+ return result ?? guardPolicyRefusal('guard policy checkout authority is copied, changed, expired or already consumed');
632
+ }
633
+ const boundaries = result.result.policy;
634
+ const projectCommands = [];
635
+ for (const { index: siblingIndex, sibling, root: checkoutRoot } of checkoutRoots) {
636
+ const policies = guardConfig.environment.install_policies;
637
+ if (!policies.some(policy => policy.toolchain === sibling.toolchain && (policy.build || policy.test)))
638
+ continue;
639
+ const checkoutId = deriveCheckoutId({ checkoutPath: checkoutRoot });
640
+ const evaluatedAt = Date.now();
641
+ const observation = await observeInstallPolicyLockfiles({ source: loaded.source, siblingIndex, siblingSlug: sibling.slug,
642
+ checkoutRoot, checkoutId, policies, observedAt: new Date(evaluatedAt).toISOString() });
643
+ const resolution = await withInstallPolicyProjection(observation, {
644
+ source: loaded.source, siblingIndex, siblingSlug: sibling.slug, checkoutRoot, checkoutId, policies, evaluatedAt,
645
+ }, projection => resolveInstallPolicies({ source: loaded.source, siblingIndex, sibling, checkoutRoot, checkoutId, policies, projection }));
646
+ if (resolution?.kind !== 'complete' || resolution.bindings.length !== 1) {
647
+ return guardPolicyRefusal(`guard project command policy is unavailable for ${sibling.slug}`);
648
+ }
649
+ for (const kind of ['build', 'test']) {
650
+ const argv = resolution.bindings[0].policy[kind];
651
+ if (!argv)
652
+ continue;
653
+ if (renderGuardProjectCommand(argv).kind !== 'complete')
654
+ return guardPolicyRefusal(`guard ${kind} command exceeds its rendering bound`);
655
+ projectCommands.push({ executable: argv[0], checkoutRoot, kind, argv: argv.slice(1) });
656
+ }
657
+ }
658
+ if (projectCommands.length > GUARD_INPUT_LIMITS.invocations)
659
+ return guardPolicyRefusal('guard candidates exceed the invocation bound');
660
+ const current = await loadCockpitConfig(loaded.source.path);
661
+ const revalidated = current.kind === 'complete' ? consumeLoadedConfigProjection(current.result) : undefined;
662
+ if (!revalidated || !sameSource(revalidated.source, loaded.source))
663
+ return guardPolicyRefusal('guard configuration source changed during command construction');
664
+ for (const [path, before] of identities) {
665
+ const after = lstatSync(path);
666
+ if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode
667
+ || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || before.size !== after.size) {
668
+ return guardPolicyRefusal('guard root or instance identity changed during command construction');
669
+ }
670
+ }
671
+ const policy = { ...boundaries, projectCommands };
672
+ return { kind: 'complete', status: 0, diagnostics: [], result: { policy } };
673
+ }
674
+ catch (error) {
675
+ return guardPolicyRefusal(`guard policy construction failed: ${errorCause(error)}`);
676
+ }
677
+ }