@b00dea/cockpit 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -1
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +76 -1
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,123 @@
1
+ // Owns the machine-local guard identity file: its path, its bytes and how `sync` writes it.
2
+ // Tracked guard bytes must be identical on every clone, so everything that names this machine
3
+ // (Node, the installed cockpit module, root and configuration device and inode numbers) lives here,
4
+ // in a directory that ignores itself. It decides nothing: the guard application authenticates the
5
+ // file against the running process and the published surfaces before any policy is resolved.
6
+ import { exactKeys } from '../exact-keys.js';
7
+ import { randomBytes } from 'node:crypto';
8
+ import { lstat, mkdir, rename, rm, writeFile } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { readBoundedInput } from '../config/load.js';
11
+ import { errorCause, hasErrorCode } from '../environment/contract.js';
12
+ export const GUARD_LOCAL_PATHS = Object.freeze({
13
+ directory: '.cockpit/local',
14
+ identity: '.cockpit/local/guard-identity',
15
+ ignore: '.cockpit/local/.gitignore',
16
+ });
17
+ // The guard script reads this file with `read` one line at a time, so the layout is lines rather
18
+ // than JSON: a format line, the two executables it launches, then the identity the application checks.
19
+ const FORMAT = 'cockpit-guard-local/v1';
20
+ const LOCAL_FILE_MAX_BYTES = 65_536;
21
+ const KEYS = ['root', 'rootDevice', 'rootInode', 'configPath', 'configSha256', 'configDevice', 'configInode',
22
+ 'node', 'nodeSha256', 'nodeFingerprint', 'module', 'moduleSha256'];
23
+ const IGNORE_BYTES = '# cockpit-owned: machine-local files, written by `cockpit sync`, never committed\n*\n';
24
+ export function encodeGuardLocalIdentity(identity) {
25
+ const ordered = Object.fromEntries(KEYS.map(key => [key, identity[key]]));
26
+ return `${FORMAT}\n${identity.node}\n${identity.module}\n${JSON.stringify(ordered)}\n`;
27
+ }
28
+ /** The exact identity the bytes carry, or undefined when they are not a complete current-format file. */
29
+ export function parseGuardLocalIdentity(bytes) {
30
+ const lines = bytes.split('\n');
31
+ if (lines.length !== 5 || lines[0] !== FORMAT || lines[4] !== '')
32
+ return undefined;
33
+ let value;
34
+ try {
35
+ value = JSON.parse(lines[3]);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof SyntaxError)
39
+ return undefined; // not the current format
40
+ throw error;
41
+ }
42
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
43
+ return undefined;
44
+ const record = value;
45
+ if (!exactKeys(record, KEYS) || !KEYS.every(key => typeof record[key] === 'string'))
46
+ return undefined;
47
+ const identity = record;
48
+ return identity.node === lines[1] && identity.module === lines[2] && encodeGuardLocalIdentity(identity) === bytes ? identity : undefined;
49
+ }
50
+ async function observeLocalFile(path) {
51
+ try {
52
+ const before = await lstat(path);
53
+ if (!before.isFile() || before.isSymbolicLink() || before.size > LOCAL_FILE_MAX_BYTES)
54
+ return { kind: 'invalid' };
55
+ const read = await readBoundedInput(path);
56
+ if (read.kind !== 'complete' || read.bytes.length > LOCAL_FILE_MAX_BYTES)
57
+ return { kind: 'invalid' };
58
+ const after = await lstat(path);
59
+ if (!after.isFile() || after.isSymbolicLink() || after.dev !== before.dev || after.ino !== before.ino
60
+ || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs)
61
+ return { kind: 'invalid' };
62
+ return { kind: 'complete', status: after, bytes: read.bytes };
63
+ }
64
+ catch (error) {
65
+ return hasErrorCode(error, 'ENOENT') ? { kind: 'absent' } : { kind: 'invalid' };
66
+ }
67
+ }
68
+ async function directoryAt(path, create) {
69
+ try {
70
+ const status = await lstat(path);
71
+ return status.isDirectory() && !status.isSymbolicLink() ? undefined : `${path} is not a plain directory`;
72
+ }
73
+ catch (error) {
74
+ if (!hasErrorCode(error, 'ENOENT') || !create)
75
+ return `${path} is unavailable (${error.code ?? 'unknown error'})`;
76
+ }
77
+ await mkdir(path, { mode: 0o700 });
78
+ return directoryAt(path, false);
79
+ }
80
+ // Rename replaces a planted symlink at the destination instead of writing through it.
81
+ async function replaceFile(directory, name, bytes) {
82
+ const temporary = join(directory, `.${name}.${randomBytes(8).toString('hex')}.tmp`);
83
+ try {
84
+ await writeFile(temporary, bytes, { flag: 'wx', mode: 0o600 });
85
+ await rename(temporary, join(directory, name));
86
+ }
87
+ finally {
88
+ await rm(temporary, { force: true });
89
+ }
90
+ }
91
+ /** Publish the identity after the tracked surfaces it authenticates; the reason names what failed. */
92
+ export async function writeGuardLocalIdentity(root, identity) {
93
+ return writeCockpitLocalFiles(root, [{ name: 'guard-identity', bytes: encodeGuardLocalIdentity(identity) }]);
94
+ }
95
+ /** Replace machine-local files under `.cockpit/local`, a directory that ignores itself; the reason names what failed. */
96
+ export async function writeCockpitLocalFiles(root, files) {
97
+ let current = GUARD_LOCAL_PATHS.directory;
98
+ try {
99
+ const parent = await directoryAt(join(root, '.cockpit'), false);
100
+ if (parent)
101
+ return { kind: 'refusal', message: parent };
102
+ const directory = join(root, GUARD_LOCAL_PATHS.directory);
103
+ const local = await directoryAt(directory, true);
104
+ if (local)
105
+ return { kind: 'refusal', message: local };
106
+ current = GUARD_LOCAL_PATHS.ignore;
107
+ const ignore = join(root, GUARD_LOCAL_PATHS.ignore);
108
+ const currentIgnore = await observeLocalFile(ignore);
109
+ if (currentIgnore.kind !== 'complete' || currentIgnore.bytes.toString('utf8') !== IGNORE_BYTES) {
110
+ await replaceFile(directory, '.gitignore', IGNORE_BYTES);
111
+ }
112
+ for (const file of files) {
113
+ current = `${GUARD_LOCAL_PATHS.directory}/${file.name}`;
114
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(file.name))
115
+ return { kind: 'refusal', message: `${current} is not a plain local file name` };
116
+ await replaceFile(directory, file.name, file.bytes);
117
+ }
118
+ return { kind: 'complete' };
119
+ }
120
+ catch (error) {
121
+ return { kind: 'refusal', message: `${current} could not be written (${errorCause(error)})` };
122
+ }
123
+ }
@@ -0,0 +1,13 @@
1
+ import type { GuardDecision } from './evaluator.js';
2
+ import type { GuardFileContent, ParsedInvocation } from './invocation-parser.js';
3
+ /**
4
+ * The denial for a tool call that would change the pinned policy tables, or undefined. `isConfig`
5
+ * says whether a lexically resolved path is the configuration, by spelling or by file identity.
6
+ */
7
+ export declare function pinnedPolicyDenial(input: Readonly<{
8
+ tool: string;
9
+ cwd: string;
10
+ content?: GuardFileContent;
11
+ paths: readonly string[];
12
+ invocations: readonly ParsedInvocation[];
13
+ }>, current: string, isConfig: (path: string) => boolean): GuardDecision | undefined;
@@ -0,0 +1,175 @@
1
+ // Owns the guard's pin on the autonomy policy: an agent tool call that would change cockpit.toml's
2
+ // [work] (with [work.gates]) or [autonomy] table, or [mcp] while a person approves MCP servers, is a
3
+ // hard denial. An edit, write or patch is applied to the current text and the tables compared; a
4
+ // shell command that names the file and is not a proven read, a Git record or cockpit's --config
5
+ // operand cannot be applied, so it is denied too.
6
+ // The decision is pure: the guard application supplies the current text and which targets are the
7
+ // configuration under another name.
8
+ import { posix } from 'node:path';
9
+ import { isDeepStrictEqual } from 'node:util';
10
+ import { pinnedPolicyTables } from '../autonomy.js';
11
+ import { classifyReadOnlyInvocation, cockpitLauncherPrefix } from './read-only.js';
12
+ const denial = (reason) => ({ decision: 'deny', reason });
13
+ const PINNED = '[work], [work.gates] and [autonomy] (and [mcp] while [autonomy] mcp_approve is not agent)';
14
+ /** Applies one `*** Update File` section of a patch to `text`, or undefined when a hunk does not match exactly once. */
15
+ function applyPatchSection(text, lines) {
16
+ let result = text;
17
+ const hunks = [];
18
+ for (const line of lines) {
19
+ if (line.startsWith('@@'))
20
+ hunks.push([]);
21
+ else if (line !== '*** End of File')
22
+ (hunks.at(-1) ?? (hunks.push([]), hunks.at(-1))).push(line);
23
+ }
24
+ for (const hunk of hunks) {
25
+ const before = hunk.filter(line => !line.startsWith('+')).map(line => line.slice(1)).join('\n');
26
+ const after = hunk.filter(line => !line.startsWith('-')).map(line => line.slice(1)).join('\n');
27
+ const first = result.indexOf(before);
28
+ if (first < 0 || result.indexOf(before, first + 1) >= 0)
29
+ return undefined;
30
+ result = result.slice(0, first) + after + result.slice(first + before.length);
31
+ }
32
+ return result;
33
+ }
34
+ function proposedText(content, isConfig, current) {
35
+ if (content.kind === 'write')
36
+ return isConfig(content.path) ? content.content : 'untouched';
37
+ if (content.kind === 'replace') {
38
+ if (!isConfig(content.path))
39
+ return 'untouched';
40
+ if (!content.old)
41
+ return undefined;
42
+ const first = current.indexOf(content.old);
43
+ if (first < 0 || !content.all && current.indexOf(content.old, first + 1) >= 0)
44
+ return undefined;
45
+ return content.all ? current.split(content.old).join(content.new) : current.slice(0, first) + content.new + current.slice(first + content.old.length);
46
+ }
47
+ const lines = content.text.trimEnd().split('\n').slice(1, -1);
48
+ let section;
49
+ let target;
50
+ let touched = false;
51
+ let next = current;
52
+ const flush = () => {
53
+ if (!section || !target)
54
+ return true;
55
+ if (target === 'add') {
56
+ next = section.map(line => line.slice(1)).join('\n') + '\n';
57
+ return true;
58
+ }
59
+ const applied = applyPatchSection(next, section);
60
+ if (applied === undefined)
61
+ return false;
62
+ next = applied;
63
+ return true;
64
+ };
65
+ for (const line of lines) {
66
+ const header = /^\*\*\* (Add File|Update File|Delete File|Move to): (.+)$/.exec(line);
67
+ if (!header) {
68
+ section?.push(line);
69
+ continue;
70
+ }
71
+ if (!flush())
72
+ return undefined;
73
+ section = undefined;
74
+ target = undefined;
75
+ if (!isConfig(header[2]))
76
+ continue;
77
+ touched = true;
78
+ // Deleting or moving the configuration is the structural denial's; either way its tables would change.
79
+ if (header[1] === 'Delete File' || header[1] === 'Move to')
80
+ return undefined;
81
+ section = [];
82
+ target = header[1] === 'Add File' ? 'add' : 'update';
83
+ }
84
+ if (!flush())
85
+ return undefined;
86
+ return touched ? next : 'untouched';
87
+ }
88
+ const GIT_RECORDING = new Set(['add', 'commit', 'diff', 'log', 'show', 'status', 'blame']);
89
+ // Only the `git` the command search path finds: a path spelled `git` may be any program.
90
+ function recordsWithoutWriting(invocation) {
91
+ if (invocation.executable !== 'git')
92
+ return false;
93
+ const words = invocation.argv;
94
+ for (let index = 0; index < words.length; index++) {
95
+ const word = words[index];
96
+ if (word === '-C' || word === '-c') {
97
+ index++;
98
+ continue;
99
+ }
100
+ if (word.startsWith('-'))
101
+ return false;
102
+ return GIT_RECORDING.has(word);
103
+ }
104
+ return false;
105
+ }
106
+ /**
107
+ * The invocations the pin judges. A cockpit session runs the words after its `--` as a command of
108
+ * its own, so that command is judged as one more invocation and the session by the words before it.
109
+ */
110
+ function judgedInvocations(invocations) {
111
+ const judged = [];
112
+ const pending = [...invocations];
113
+ for (let invocation = pending.shift(); invocation; invocation = pending.shift()) {
114
+ const prefix = invocation.executable === null ? undefined : cockpitLauncherPrefix(invocation.executable, invocation.argv);
115
+ const separator = prefix !== undefined && invocation.argv[prefix] === 'session' ? invocation.argv.indexOf('--', prefix) : -1;
116
+ if (separator < 0) {
117
+ judged.push(invocation);
118
+ continue;
119
+ }
120
+ judged.push({ ...invocation, argv: invocation.argv.slice(0, separator) });
121
+ const command = invocation.argv.slice(separator + 1);
122
+ if (command.length)
123
+ pending.push({ executable: command[0], argv: command.slice(1), cwd: invocation.cwd, wrapped: false, redirects: [],
124
+ words: command.map((value, index) => ({ role: index ? 'argument' : 'executable', value })) });
125
+ }
126
+ return judged;
127
+ }
128
+ /**
129
+ * The denial for a tool call that would change the pinned policy tables, or undefined. `isConfig`
130
+ * says whether a lexically resolved path is the configuration, by spelling or by file identity.
131
+ */
132
+ export function pinnedPolicyDenial(input, current, isConfig) {
133
+ const resolved = (cwd) => (path) => isConfig(posix.resolve(cwd, path));
134
+ if (input.tool === 'Bash') {
135
+ for (const invocation of judgedInvocations(input.invocations)) {
136
+ const names = resolved(invocation.cwd);
137
+ // `--out=cockpit.toml` names the file as surely as a bare operand does.
138
+ const naming = invocation.argv.flatMap((word, index) => names(word) || word.includes('=') && names(word.slice(word.indexOf('=') + 1)) ? [index] : []);
139
+ const writes = invocation.redirects.some(redirect => redirect.write);
140
+ if (!naming.length && !invocation.redirects.some(redirect => redirect.write && names(redirect.target)))
141
+ continue;
142
+ // A proven read leaves the file as it is, and staging or committing it records the text the
143
+ // guard already checked.
144
+ if (!writes && (classifyReadOnlyInvocation(invocation) || recordsWithoutWriting(invocation)))
145
+ continue;
146
+ // Installed cockpit reads the file it is given with --config, but a path or `node <script>`
147
+ // spells any program as well as cockpit. Whether it starts cockpit is the evaluator's
148
+ // launcher proof, so only the --config operand is left to it; naming the file anywhere
149
+ // else is denied whatever the spelling starts.
150
+ const prefix = invocation.executable === null ? undefined : cockpitLauncherPrefix(invocation.executable, invocation.argv);
151
+ const configOperand = (index) => prefix !== undefined && index >= prefix
152
+ && (invocation.argv[index].startsWith('--config=') || index > prefix && invocation.argv[index - 1] === '--config');
153
+ if (!writes && naming.every(configOperand))
154
+ continue;
155
+ return denial(`Refusing a shell command that names the cockpit configuration: the guard cannot tell whether it changes ${PINNED}, which only a person changes. Edit other tables with your editing tool, which the guard can check.`);
156
+ }
157
+ return undefined;
158
+ }
159
+ if (!input.paths.some(resolved(input.cwd)))
160
+ return undefined;
161
+ if (!input.content)
162
+ return denial(`Refusing an edit of the cockpit configuration whose new text the guard was not given, so it cannot show ${PINNED} unchanged.`);
163
+ const proposed = proposedText(input.content, resolved(input.cwd), current);
164
+ if (proposed === 'untouched')
165
+ return undefined;
166
+ if (proposed === undefined)
167
+ return denial(`Refusing an edit of the cockpit configuration that does not apply exactly once to its current text, so the guard cannot show ${PINNED} unchanged.`);
168
+ const before = pinnedPolicyTables(current);
169
+ const after = pinnedPolicyTables(proposed);
170
+ if (!before || !after)
171
+ return denial(`Refusing an edit that leaves the cockpit configuration unparseable, so the guard cannot show ${PINNED} unchanged.`);
172
+ if (!isDeepStrictEqual(before, after))
173
+ return denial(`Refusing to change ${PINNED} in the cockpit configuration: who may pass a gate is a person's decision, made outside the agent. Ask a person to edit it and run sync.`);
174
+ return undefined;
175
+ }
@@ -0,0 +1,62 @@
1
+ import { type ExecutionStep } from '../environment/contract.js';
2
+ import type { ResolvedExecutable } from '../environment/executable.js';
3
+ import { type ExecuteProbeOutcome } from '../environment-exec/execute.js';
4
+ /**
5
+ * The running cockpit package and every package it loads, each under the spelling it was found by
6
+ * and its real path, plus the consumer's `node_modules` spelling of the package when that is a link
7
+ * to it. The guard authenticates only its entry point, so all of these are controls.
8
+ */
9
+ export declare function guardInstalledDirectories(packageRoot: string, consumerRoot: string): readonly string[];
10
+ /**
11
+ * The file whose identity proves that `executable` with the launcher `words` after it, run in `cwd`,
12
+ * starts `launcher`, the installed cockpit the guard runs from, or undefined when it may start
13
+ * something else. A path, or the script `node` is given, qualifies when it resolves to the launcher;
14
+ * the bare name when the first `cockpit` on the command search path does. `npx` qualifies only in
15
+ * `root`, where npm runs a `bin` the root package.json declares before any installed one and applies
16
+ * the root `.npmrc`, whose `script-shell` and `node-options` run other code first; with neither
17
+ * present it runs the root's `node_modules/.bin/cockpit`, which must resolve to the launcher.
18
+ */
19
+ export declare function guardLauncherEvidence(executable: string, words: readonly string[], cwd: string, root: string, launcher: string, searchPath: string): string | undefined;
20
+ /**
21
+ * Absolute search path entries, and their real paths, that lie inside an allowed root. A relative
22
+ * entry resolves against whatever directory a command runs in, so it names no fixed location.
23
+ */
24
+ export declare function guardSearchDirectories(searchPath: string, allowedRoots: readonly string[]): readonly string[];
25
+ /** The first entry of `searchPath` that is not absolute, an empty one included, or null when there is none. */
26
+ export declare function guardSearchPathRelativeEntry(searchPath: string): string | null;
27
+ /**
28
+ * Every path at or below `path` whose case-folded name starts with `.env` or is `.secrets`, of any
29
+ * type, without following links, as a recursive search would reach it; null once the walk passes
30
+ * `entryLimit` entries or finds more than the sensitive path bound. A missing path has none.
31
+ */
32
+ export declare function guardSensitivePathsBelow(path: string, entryLimit?: number): readonly string[] | null;
33
+ export type GuardExecute = (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
34
+ /**
35
+ * The paths a default ripgrep search of `path` reads that are named `.env*`, lie below a `.secrets`
36
+ * directory or are one of `watched`, each reported at its first such name, as Git lists what the
37
+ * enclosing repository does not ignore; null past the sensitive path bound. Undefined when Git
38
+ * did not answer or its list cannot stand for ripgrep's: outside a repository, through a
39
+ * nested repository or submodule, below an `.ignore` or `.rgignore` file, which ripgrep reads and Git
40
+ * does not, or when the search names an ignored directory, whose children ripgrep may still read.
41
+ *
42
+ * Only `.gitignore` files are applied: ripgrep reads those, the repository's exclude file and the
43
+ * global excludes file, but finds the global one by other rules than Git, so Git applying fewer
44
+ * lists more. Hidden paths are kept: a type such as `rg -t sh`, which matches `.env`, reads them.
45
+ * Undefined as well when a `.gitignore` Git reads holds a line the two may match differently.
46
+ */
47
+ export declare function guardUnignoredSensitivePaths(path: string, watched: readonly string[], git: ResolvedExecutable, execute: GuardExecute): Promise<readonly string[] | null | undefined>;
48
+ /**
49
+ * Every path `repository`'s index holds that is named `.env*`, lies below a `.secrets` or `.env*`
50
+ * directory or is one of `watched`, or null past the sensitive path bound or when Git did not
51
+ * answer. Only the index is asked: a file removed from it stays in history, which this does not walk.
52
+ */
53
+ export declare function guardTrackedSensitivePaths(repository: string, watched: readonly string[], git: ResolvedExecutable, execute: GuardExecute): Promise<readonly string[] | null>;
54
+ /**
55
+ * Identities of every file in each case-folded spelling of `env/.secrets` below `root`, and whether
56
+ * any has a second hard link; `identities` is null once more than the bound of entries was seen,
57
+ * which leaves every read unproven rather than silently uncovered.
58
+ */
59
+ export declare function guardSecretFiles(root: string): {
60
+ readonly identities: readonly string[] | null;
61
+ readonly linked: boolean;
62
+ };