@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,266 @@
1
+ 'use strict'
2
+
3
+ // Renders agent surfaces for packages/cockpit/src/surfaces/generator-adapter.ts by running the pinned
4
+ // Rulesync bundle (libexec/rulesync-generator.cjs) over each staged job. Runs from a staged snapshot,
5
+ // so it must stay self-contained.
6
+ //
7
+ // argv: <request JSON> <bundle path>. The request carries the pass root and its identity, the bundle
8
+ // digest and bound, the output `limits`, and one entry per job (target, features, staged roots and
9
+ // their identities, the warning source digest and the receipt path to write).
10
+ // For each job the worker writes a private receipt file ({ version, target, features, reportedPaths,
11
+ // actualFiles, warnings }) and reports on stdout one summary line { version: 1, jobs: [...] } holding
12
+ // each receipt's size and digest, or the job's error cause. The adapter reads every receipt back
13
+ // through a separate reader before it trusts any output.
14
+ //
15
+ // The bundle is hashed and then compiled from those same bytes, never re-read by path, so a bundle
16
+ // replaced between the check and the load is not what runs.
17
+ const fs = require('node:fs')
18
+ const crypto = require('node:crypto')
19
+ const Module = require('node:module')
20
+ const path = require('node:path')
21
+
22
+ const digest = value => crypto.createHash('sha256').update(value).digest('hex')
23
+ const safe = value => typeof value === 'string' && value.length > 0 && !path.isAbsolute(value) && !value.includes('\\') && !value.includes('\0')
24
+ && value.split('/').every(part => part && part !== '.' && part !== '..')
25
+ const sameDirectory = (value, expected) => value.isDirectory() && !value.isSymbolicLink() && value.dev === expected.dev && value.ino === expected.ino
26
+
27
+ function authenticateBundle(bundlePath, expectedSha256, maxBytes) {
28
+ const before = fs.lstatSync(bundlePath)
29
+ if (!before.isFile() || before.isSymbolicLink() || before.size > maxBytes || fs.constants.O_NOFOLLOW === undefined) throw new Error('rulesync bundle file changed')
30
+ const fd = fs.openSync(bundlePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
31
+ try {
32
+ const opened = fs.fstatSync(fd)
33
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > maxBytes) throw new Error('rulesync bundle identity changed')
34
+ const chunks = []
35
+ const chunk = Buffer.allocUnsafe(65536)
36
+ let total = 0
37
+ while (true) {
38
+ const count = fs.readSync(fd, chunk, 0, Math.min(chunk.length, maxBytes - total + 1), null)
39
+ if (count === 0) break
40
+ total += count
41
+ if (total > maxBytes) throw new Error('rulesync bundle limit exceeded')
42
+ chunks.push(Buffer.from(chunk.subarray(0, count)))
43
+ }
44
+ const after = fs.fstatSync(fd)
45
+ if (total !== opened.size || after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
46
+ || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) throw new Error('rulesync bundle changed while reading')
47
+ const bytes = Buffer.concat(chunks, total)
48
+ if (digest(bytes) !== expectedSha256) throw new Error('rulesync bundle digest changed')
49
+ return bytes
50
+ } finally { fs.closeSync(fd) }
51
+ }
52
+
53
+ function loadAuthenticatedBundle(bundlePath, bytes) {
54
+ const loaded = new Module(bundlePath, module)
55
+ loaded.filename = bundlePath
56
+ loaded.paths = Module._nodeModulePaths(path.dirname(bundlePath))
57
+ loaded._compile(new TextDecoder('utf-8', { fatal: true }).decode(bytes), bundlePath)
58
+ return loaded.exports
59
+ }
60
+
61
+ function inventory(root, limits) {
62
+ const files = []
63
+ let total = 0
64
+ const walk = (current, prefix = '') => {
65
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
66
+ if (!safe(entry.name)) throw new Error('unsafe generated entry')
67
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name
68
+ const target = path.join(current, entry.name)
69
+ const status = fs.lstatSync(target)
70
+ if (status.isSymbolicLink()) throw new Error('generated symbolic link')
71
+ if (status.isDirectory()) walk(target, relative)
72
+ else if (status.isFile()) {
73
+ if (status.size > limits.fileMaxBytes) throw new Error('generated file limit exceeded')
74
+ const bytes = fs.readFileSync(target)
75
+ total += bytes.length
76
+ if (files.length >= limits.outputMaxFiles || total > limits.outputMaxBytes) throw new Error('generated output limit exceeded')
77
+ files.push({ path: relative, bytes: bytes.length, sha256: digest(bytes) })
78
+ } else throw new Error('generated non-regular entry')
79
+ }
80
+ }
81
+ walk(root)
82
+ return files.sort((left, right) => left.path.localeCompare(right.path))
83
+ }
84
+
85
+ const describeEntries = entries => entries.map(({ label, reason }) => `'${label}' — ${reason}`).join('; ')
86
+ const quoteValues = values => values.map(value => JSON.stringify(value)).join(', ')
87
+ // Cockpit's wording names each value the person is trusting, and only settings that grant something:
88
+ // an empty list grants nothing, and warning about it on every sync taught people to skip the warning.
89
+ function warningMessage(entries) {
90
+ const one = entries.length === 1
91
+ return `Claude Code permissions: writing ${entries.length} trust-affecting ${one ? 'setting' : 'settings'} to .claude/settings.json; `
92
+ + `review ${one ? 'it' : 'them'} as you would a hook because synced agent settings can widen filesystem access, approve project servers or run commands without asking. `
93
+ + `${describeEntries(entries)}.`
94
+ }
95
+ // Rulesync's own spelling of its warning, which counts a present empty server list; it is accepted
96
+ // only to authenticate the generated settings and is replaced by cockpit's wording above.
97
+ function nativeWarningMessage(entries) {
98
+ const one = entries.length === 1
99
+ return `Claude Code permissions: writing ${entries.length} trust-affecting ${one ? 'setting' : 'settings'} to .claude/settings.json; `
100
+ + `review ${one ? 'it' : 'them'} as you would a hook, especially if this permissions file came from '${['rulesync', 'fetch'].join(' ')}'. `
101
+ + `${describeEntries(entries)}.`
102
+ }
103
+ const knownWarningEntries = [
104
+ { label: 'permissions.additionalDirectories', reason: 'moves the boundary of what Claude Code may read and edit outside the project' },
105
+ { label: 'enabledMcpjsonServers', reason: 'auto-approves the named servers in the project `.mcp.json`' },
106
+ ]
107
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
108
+ const sameStrings = (left, right) => Array.isArray(left) && Array.isArray(right) && left.length === right.length
109
+ && left.every((value, index) => typeof value === 'string' && value === right[index])
110
+ const stringList = value => Array.isArray(value) && value.every(item => typeof item === 'string')
111
+
112
+ // A captured warning is admitted only when it is exactly the warning the staged permissions source
113
+ // and the generated settings justify; anything else is treated as a generator diagnostic.
114
+ function authenticateWarnings({ job, captured, actualFiles }) {
115
+ if (captured.length === 0) return []
116
+ if (job.target !== 'claudecode' || job.warningSourceSha256 === null) return undefined
117
+ const sourcePath = path.join(job.inputRoot, 'permissions.jsonc')
118
+ if (!fs.existsSync(sourcePath)) return undefined
119
+ const sourceBytes = fs.readFileSync(sourcePath)
120
+ if (digest(sourceBytes) !== job.warningSourceSha256 || !actualFiles.some(file => file.path === '.claude/settings.json')) return undefined
121
+ let source
122
+ let settings
123
+ try {
124
+ source = JSON.parse(sourceBytes.toString('utf8'))
125
+ settings = JSON.parse(fs.readFileSync(path.join(job.outputRoot, '.claude/settings.json'), 'utf8'))
126
+ } catch {
127
+ // Unparseable settings cannot justify the warning; the caller reports the warning text itself.
128
+ return undefined
129
+ }
130
+ if (!record(source) || !record(settings)) return undefined
131
+ const claude = source.claudecode
132
+ if (!record(claude)) return undefined
133
+ const sourceAdditionalDirectories = record(claude.permissions) ? claude.permissions.additionalDirectories : undefined
134
+ const sourceEnabledMcpjsonServers = claude.enabledMcpjsonServers
135
+ const additionalDirectories = record(settings.permissions) ? settings.permissions.additionalDirectories : undefined
136
+ const enabledMcpjsonServers = settings.enabledMcpjsonServers
137
+ if ((additionalDirectories !== undefined || sourceAdditionalDirectories !== undefined) && !sameStrings(additionalDirectories, sourceAdditionalDirectories)
138
+ || (enabledMcpjsonServers !== undefined || sourceEnabledMcpjsonServers !== undefined) && !sameStrings(enabledMcpjsonServers, sourceEnabledMcpjsonServers)) return undefined
139
+ const allow = record(settings.permissions) ? settings.permissions.allow : undefined
140
+ const entries = []
141
+ if (Array.isArray(additionalDirectories) && additionalDirectories.length > 0) entries.push(knownWarningEntries[0])
142
+ if (Array.isArray(enabledMcpjsonServers)) entries.push(knownWarningEntries[1])
143
+ if (entries.length === 0 || additionalDirectories !== undefined && !stringList(additionalDirectories)
144
+ || enabledMcpjsonServers !== undefined && !stringList(enabledMcpjsonServers)
145
+ || allow !== undefined && !stringList(allow)) return undefined
146
+ if (captured.length !== 1 || captured[0] !== nativeWarningMessage(entries)) return undefined
147
+ const granted = [
148
+ ...additionalDirectories?.length ? [{ label: 'permissions.additionalDirectories', reason: `${knownWarningEntries[0].reason}: ${quoteValues(additionalDirectories)}` }] : [],
149
+ ...enabledMcpjsonServers?.length ? [{ label: 'enabledMcpjsonServers', reason: `${knownWarningEntries[1].reason}: ${quoteValues(enabledMcpjsonServers)}` }] : [],
150
+ ...allow?.length ? [{ label: 'permissions.allow', reason: `lets Claude Code run ${allow.length} command ${allow.length === 1 ? 'pattern' : 'patterns'} without asking; the file lists each one` }] : [],
151
+ ]
152
+ return granted.length === 0 ? []
153
+ : [{ severity: 'warning', code: 'SURFACE_GENERATOR_TRUST_WARNING', target: job.target, path: '.claude/settings.json', message: warningMessage(granted) }]
154
+ }
155
+
156
+ function writeReceipt(target, value, limits) {
157
+ const bytes = Buffer.from(`${JSON.stringify(value)}\n`)
158
+ if (bytes.length > limits.stdoutMaxBytes || fs.constants.O_NOFOLLOW === undefined) throw new Error('generator receipt limit exceeded')
159
+ const fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600)
160
+ try { fs.writeFileSync(fd, bytes) } finally { fs.closeSync(fd) }
161
+ return { bytes: bytes.length, sha256: digest(bytes) }
162
+ }
163
+
164
+ async function generateJob(job, bundleBytes, bundlePath, limits) {
165
+ let failureCause = 'nonzero'
166
+ const captured = []
167
+ let capturedBytes = 0
168
+ const stderr = []
169
+ let stderrBytes = 0
170
+ // Rulesync reports problems on stderr and console.warn; both are captured per job, bounded, and
171
+ // turned into that job's failure instead of reaching cockpit's own output.
172
+ const originalStderrWrite = process.stderr.write
173
+ process.stderr.write = (chunk, encoding, callback) => {
174
+ const bytes = typeof chunk === 'string' ? Buffer.from(chunk, typeof encoding === 'string' ? encoding : undefined) : Buffer.from(chunk)
175
+ const remaining = limits.stderrMaxBytes - stderrBytes
176
+ if (remaining > 0) stderr.push(bytes.subarray(0, remaining))
177
+ stderrBytes += bytes.length
178
+ const done = typeof encoding === 'function' ? encoding : callback
179
+ if (typeof done === 'function') queueMicrotask(done)
180
+ return true
181
+ }
182
+ try {
183
+ const cwdStatus = fs.lstatSync(job.cwd)
184
+ const outputStatus = fs.lstatSync(job.outputRoot)
185
+ if (!sameDirectory(cwdStatus, job.cwdIdentity) || !sameDirectory(outputStatus, job.identity)) throw new Error('job root identity changed')
186
+ process.chdir(job.cwd)
187
+ if (!sameDirectory(fs.lstatSync('.'), job.cwdIdentity)) throw new Error('job working directory changed')
188
+ const root = loadAuthenticatedBundle(bundlePath, bundleBytes)
189
+ if (typeof root.generate !== 'function') throw new Error('rulesync root generate export unavailable')
190
+ const originalWarn = console.warn
191
+ console.warn = (...args) => {
192
+ const warning = args.length === 1 && typeof args[0] === 'string' ? args[0] : args.map(value => String(value)).join(' ')
193
+ const bytes = Buffer.byteLength(warning)
194
+ if (captured.length >= limits.warningCount || bytes > limits.warningBytes || capturedBytes + bytes > limits.warningTotalBytes) {
195
+ failureCause = 'stderr'
196
+ captured.splice(0, captured.length, 'surface generator emitted an unbounded diagnostic')
197
+ return
198
+ }
199
+ captured.push(warning)
200
+ capturedBytes += bytes
201
+ }
202
+ let result
203
+ try {
204
+ result = await root.generate({
205
+ configPath: 'rulesync.jsonc', inputRoots: [job.inputRoot], outputRoots: [job.outputRoot], targets: [job.target],
206
+ features: job.features, global: false, delete: false, silent: false,
207
+ })
208
+ } catch (error) {
209
+ if (captured.length > 0) {
210
+ failureCause = 'stderr'
211
+ throw new Error(captured.join('\n'))
212
+ }
213
+ throw error
214
+ } finally { console.warn = originalWarn }
215
+ if (stderrBytes > 0) {
216
+ failureCause = 'stderr'
217
+ throw new Error(Buffer.concat(stderr).toString('utf8'))
218
+ }
219
+ if (result.sourceLoadFailed !== false || !Array.isArray(result.sourceLoadFailedFeatures) || result.sourceLoadFailedFeatures.length !== 0) {
220
+ throw new Error('rulesync could not load every requested source')
221
+ }
222
+ const features = job.features.map(feature => ({ feature, count: result[`${feature}Count`], paths: result[`${feature}Paths`] }))
223
+ const reportedPaths = [...new Set(features.flatMap(feature => feature.paths))].sort((left, right) => left.localeCompare(right))
224
+ const actualFiles = inventory(job.outputRoot, limits)
225
+ const warnings = authenticateWarnings({ job, captured, actualFiles })
226
+ if (warnings === undefined) {
227
+ failureCause = 'stderr'
228
+ throw new Error(captured.join('\n'))
229
+ }
230
+ if (!sameDirectory(fs.lstatSync(job.outputRoot), job.identity)) throw new Error('output root identity changed')
231
+ const receipt = writeReceipt(job.receiptOutput, { version: 1, target: job.target, features, reportedPaths, actualFiles, warnings }, limits)
232
+ return { id: job.id, target: job.target, kind: 'complete', ...receipt }
233
+ } catch (error) {
234
+ if (stderrBytes > 0) failureCause = 'stderr'
235
+ const message = stderrBytes > 0 ? Buffer.concat(stderr).toString('utf8') : String(error && (error.stack || error.message) || error)
236
+ return { id: job.id, target: job.target, kind: 'error', cause: failureCause, message: message.slice(0, 1024) }
237
+ } finally {
238
+ process.stderr.write = originalStderrWrite
239
+ }
240
+ }
241
+
242
+ async function main() {
243
+ const request = JSON.parse(process.argv[2])
244
+ const bundlePath = process.argv[3]
245
+ const bundleBytes = authenticateBundle(bundlePath, request.bundleSha256, request.bundleMaxBytes)
246
+ if (!sameDirectory(fs.lstatSync('.'), request.identity)) throw new Error('pass root identity changed')
247
+ const summaries = []
248
+ for (const job of request.jobs) {
249
+ try {
250
+ summaries.push(await generateJob(job, bundleBytes, bundlePath, request.limits))
251
+ } finally {
252
+ process.chdir(request.passRoot)
253
+ if (!sameDirectory(fs.lstatSync('.'), request.identity)) throw new Error('pass root changed')
254
+ }
255
+ }
256
+ process.stdout.write(`${JSON.stringify({ version: 1, jobs: summaries })}\n`)
257
+ }
258
+
259
+ if (require.main === module) {
260
+ main().catch(error => {
261
+ process.stderr.write(`${String(error && (error.stack || error.message) || error)}\n`)
262
+ process.exitCode = 1
263
+ })
264
+ }
265
+
266
+ module.exports = { authenticateBundle, loadAuthenticatedBundle }
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ // Reads back the surfaces the generator worker wrote, for packages/cockpit/src/surfaces/generator-adapter.ts.
4
+ // It is a separate process from libexec/surface-generator-worker.cjs so no code the Rulesync bundle
5
+ // loaded can shape what cockpit is told the output contains. Runs from a staged snapshot, so it must
6
+ // stay self-contained.
7
+ //
8
+ // argv: <request JSON> carrying the pass root and its identity, the output `limits`, and one entry
9
+ // per job (output root and its identity, the expected path set, the receipt path to write).
10
+ // Each job's receipt file holds { version: 1, files: [{ path, bytes, sha256, bytesBase64 }] }; stdout
11
+ // is one summary line { version: 1, jobs: [...] } with each receipt's size and digest or error cause.
12
+ // The walk moves the cwd through each directory and re-checks its identity, so a directory swapped
13
+ // for a link mid-walk is refused instead of followed.
14
+
15
+ const fs = require('node:fs')
16
+ const crypto = require('node:crypto')
17
+ const request = JSON.parse(process.argv[2])
18
+ const limits = request.limits
19
+ const c = fs.constants
20
+ const digest = value => crypto.createHash('sha256').update(value).digest('hex')
21
+ const safe = value => typeof value === 'string' && value.length > 0 && value !== '.' && value !== '..' && !value.includes('/') && !value.includes('\\') && !value.includes('\0')
22
+ const sameDirectory = (value, expected) => value.isDirectory() && !value.isSymbolicLink() && value.dev === expected.dev && value.ino === expected.ino
23
+ const samePaths = (left, right) => left.length === right.length && [...left].sort().every((value, index) => value === [...right].sort()[index])
24
+ const readFile = name => {
25
+ const before = fs.lstatSync(name)
26
+ if (!before.isFile() || before.isSymbolicLink() || before.size > limits.fileMaxBytes || c.O_NOFOLLOW === undefined) throw new Error('unsafe output file')
27
+ const fd = fs.openSync(name, c.O_RDONLY | c.O_NOFOLLOW)
28
+ try {
29
+ const opened = fs.fstatSync(fd)
30
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > limits.fileMaxBytes) throw new Error('output identity changed')
31
+ const chunks = []; const chunk = Buffer.allocUnsafe(65536); let total = 0
32
+ while (true) {
33
+ const count = fs.readSync(fd, chunk, 0, Math.min(chunk.length, limits.fileMaxBytes - total + 1), null)
34
+ if (count === 0) break
35
+ total += count
36
+ if (total > limits.fileMaxBytes) throw new Error('output file limit exceeded')
37
+ chunks.push(Buffer.from(chunk.subarray(0, count)))
38
+ }
39
+ const after = fs.fstatSync(fd)
40
+ if (total !== opened.size || after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) throw new Error('output changed while reading')
41
+ const bytes = Buffer.concat(chunks, total)
42
+ return { bytes: bytes.length, sha256: digest(bytes), bytesBase64: bytes.toString('base64') }
43
+ } finally { fs.closeSync(fd); }
44
+ }
45
+ const inventory = job => {
46
+ const files = []; let total = 0
47
+ const walk = (prefix = '') => {
48
+ const parent = fs.lstatSync('.')
49
+ if (!parent.isDirectory() || parent.isSymbolicLink()) throw new Error('unsafe output directory')
50
+ for (const entry of fs.readdirSync('.', { withFileTypes: true })) {
51
+ if (!safe(entry.name)) throw new Error('unsafe output entry')
52
+ const status = fs.lstatSync(entry.name)
53
+ const relative = prefix ? prefix + '/' + entry.name : entry.name
54
+ if (status.isSymbolicLink()) throw new Error('output symbolic link')
55
+ if (status.isDirectory()) {
56
+ process.chdir(entry.name)
57
+ if (!sameDirectory(fs.lstatSync('.'), status)) throw new Error('output directory changed')
58
+ walk(relative)
59
+ process.chdir('..')
60
+ if (!sameDirectory(fs.lstatSync('.'), parent) || !sameDirectory(fs.lstatSync(entry.name), status)) throw new Error('output directory identity changed')
61
+ } else if (status.isFile()) {
62
+ const file = readFile(entry.name)
63
+ total += file.bytes
64
+ if (files.length >= limits.outputMaxFiles || total > limits.outputMaxBytes) throw new Error('output set limit exceeded')
65
+ files.push({ path: relative, ...file })
66
+ } else throw new Error('output non-regular entry')
67
+ }
68
+ }
69
+ process.chdir(job.outputRoot)
70
+ if (!sameDirectory(fs.lstatSync('.'), job.identity)) throw new Error('output root identity changed')
71
+ walk()
72
+ if (!sameDirectory(fs.lstatSync('.'), job.identity)) throw new Error('output root changed')
73
+ files.sort((left, right) => left.path.localeCompare(right.path))
74
+ if (!samePaths(files.map(file => file.path), job.expected)) throw new Error('output path set changed')
75
+ return files
76
+ }
77
+ const writeReceipt = (target, value) => {
78
+ const bytes = Buffer.from(JSON.stringify(value) + '\n')
79
+ if (bytes.length > limits.stdoutMaxBytes || c.O_NOFOLLOW === undefined) throw new Error('reader receipt limit exceeded')
80
+ const fd = fs.openSync(target, c.O_WRONLY | c.O_CREAT | c.O_EXCL | c.O_NOFOLLOW, 0o600)
81
+ try { fs.writeFileSync(fd, bytes); } finally { fs.closeSync(fd); }
82
+ return { bytes: bytes.length, sha256: digest(bytes) }
83
+ }
84
+ const passRoot = fs.lstatSync('.')
85
+ if (!sameDirectory(passRoot, request.identity)) throw new Error('pass root identity changed')
86
+ const summaries = []
87
+ for (const job of request.jobs) {
88
+ try {
89
+ const files = inventory(job)
90
+ const receipt = writeReceipt(job.receiptOutput, { version: 1, files })
91
+ summaries.push({ id: job.id, target: job.target, kind: 'complete', ...receipt })
92
+ } catch (error) {
93
+ summaries.push({ id: job.id, target: job.target, kind: 'error', cause: 'artifact', message: String(error && (error.stack || error.message) || error).slice(0, 4096) })
94
+ } finally {
95
+ process.chdir(request.passRoot)
96
+ if (!sameDirectory(fs.lstatSync('.'), request.identity)) throw new Error('pass root changed')
97
+ }
98
+ }
99
+ process.stdout.write(JSON.stringify({ version: 1, jobs: summaries }) + '\n')
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ // Removes the private staging directory that packages/cockpit/src/surfaces/generator-adapter.ts
4
+ // created for one surface generation. Runs from a staged snapshot, so it must stay self-contained.
5
+ //
6
+ // argv: <request JSON { target, parent }>. Refuses (non-zero exit) any target that is not a
7
+ // cockpit-surfaces-* directory directly inside the named parent.
8
+ const fs = require('node:fs')
9
+ const path = require('node:path')
10
+
11
+ const request = JSON.parse(process.argv[2])
12
+ const target = request.target
13
+ if (path.dirname(target) !== request.parent || !path.basename(target).startsWith('cockpit-surfaces-')) throw new Error('cleanup target refused')
14
+ fs.rmSync(target, { recursive: true, force: true })
@@ -0,0 +1,183 @@
1
+ export interface HarnessEvidenceManifest {
2
+ harness: 'Claude Code' | 'Codex' | 'OpenCode'
3
+ version: string
4
+ date: string
5
+ capability: 'sandbox' | 'approval'
6
+ candidatePath: string
7
+ brokenPath: string
8
+ transcriptPath: string
9
+ candidateSha256: string
10
+ brokenSha256: string
11
+ transcriptSha256: string
12
+ runId: string
13
+ versionCommand: readonly string[]
14
+ helpCommand: readonly string[]
15
+ invocation: readonly string[]
16
+ forbiddenAction: string
17
+ brokenRejected: boolean
18
+ verdict: 'enforced' | 'unavailable'
19
+ syntheticFixture: string
20
+ events: {
21
+ version: HarnessProbeEvent
22
+ help: HarnessProbeEvent
23
+ brokenControl: HarnessProbeEvent
24
+ candidateControl: HarnessProbeEvent
25
+ }
26
+ }
27
+
28
+ export interface HarnessProbeEvent {
29
+ cwd: string
30
+ command: readonly string[]
31
+ exitCode: number
32
+ stream: 'stdout' | 'stderr'
33
+ code: string
34
+ result: 'completed' | 'rejected' | 'accepted' | 'refused' | 'allowed' | 'unavailable'
35
+ output: string
36
+ outputSha256: string
37
+ }
38
+
39
+ export interface HarnessEvidenceValidationOptions {
40
+ testOnlyExecutableIdentities?: Partial<Record<HarnessEvidenceManifest['harness'], readonly string[]>>
41
+ operatorExecutableIdentities?: ReadonlyMap<
42
+ HarnessEvidenceManifest['harness'],
43
+ Readonly<{ canonicalPath: string; sha256: string }>
44
+ >
45
+ }
46
+
47
+ export type SelftestCapability = 'instruction-loading' | 'guard-firing' | 'skill-parity'
48
+ export type BuiltinHarnessRef =
49
+ | { kind: 'builtin'; target: 'claudecode'; name: 'Claude Code' }
50
+ | { kind: 'builtin'; target: 'codexcli'; name: 'Codex' }
51
+ | { kind: 'builtin'; target: 'opencode'; name: 'OpenCode' }
52
+ export interface CustomHarnessRef {
53
+ kind: 'custom'
54
+ id: string
55
+ declarationSha256: string
56
+ module: string
57
+ sourceSha256: string
58
+ export: string
59
+ }
60
+ export type SelftestHarnessRef = BuiltinHarnessRef | CustomHarnessRef
61
+ export type SelftestCellKey =
62
+ | { harness: SelftestHarnessRef; capability: 'instruction-loading' | 'guard-firing' }
63
+ | { harness: SelftestHarnessRef; capability: 'skill-parity'; skill: string }
64
+ export interface SelftestExpectation {
65
+ schema: 'cockpit-harness-selftest-expectation/v1'
66
+ generatedManifestSha256: string
67
+ cells: readonly SelftestCellKey[]
68
+ shippedSkills: readonly string[]
69
+ reviewHarnesses: readonly [
70
+ { name: 'Claude Code'; nativeDiagnostics: boolean },
71
+ { name: 'Codex'; nativeDiagnostics: boolean },
72
+ ]
73
+ }
74
+ export interface SelftestProbeEvent {
75
+ cwd: string
76
+ command: readonly string[]
77
+ exitCode: number
78
+ signal: NodeJS.Signals | null
79
+ result: 'completed' | 'rejected' | 'accepted' | 'refused' | 'allowed' | 'unavailable'
80
+ code: string
81
+ stdout: string
82
+ stderr: string
83
+ stdoutSha256: string
84
+ stderrSha256: string
85
+ }
86
+ export interface BuiltinExecutionAuthority {
87
+ kind: 'native-executable'
88
+ name: 'Claude Code' | 'Codex' | 'OpenCode'
89
+ executable: string
90
+ }
91
+ export interface CustomExecutionAuthority {
92
+ kind: 'custom-module'
93
+ harnessId: string
94
+ module: string
95
+ sourceSha256: string
96
+ export: string
97
+ stepId: string
98
+ startedAt: string
99
+ finishedAt: string
100
+ }
101
+ export type SelftestExecutionAuthority = BuiltinExecutionAuthority | CustomExecutionAuthority
102
+ export interface SelftestSample { skill: string; features: readonly string[] }
103
+ export interface SelftestEvidenceRecord {
104
+ kind: 'evidence'
105
+ harness: SelftestHarnessRef
106
+ capability: SelftestCapability
107
+ generatedManifestSha256: string
108
+ verdict: 'enforced' | 'instructed' | 'unavailable'
109
+ version: string
110
+ measuredOn: string
111
+ runId: string
112
+ startedAt: string
113
+ finishedAt: string
114
+ expiresAt: string
115
+ candidatePath: string
116
+ brokenPath: string
117
+ transcriptPath: string
118
+ candidateSha256: string
119
+ brokenSha256: string
120
+ transcriptSha256: string
121
+ invocation: readonly string[]
122
+ versionCommand: readonly string[]
123
+ helpCommand: readonly string[]
124
+ candidateCode: string
125
+ brokenCode: string
126
+ sample: SelftestSample | null
127
+ authority: SelftestExecutionAuthority
128
+ events: { version: SelftestProbeEvent; help: SelftestProbeEvent; brokenControl: SelftestProbeEvent; candidateControl: SelftestProbeEvent }
129
+ }
130
+ export type SelftestHumanNeededRecord = {
131
+ kind: 'human_needed'
132
+ harness: SelftestHarnessRef
133
+ generatedManifestSha256: string
134
+ observedAt: string
135
+ reason: 'executable_missing' | 'authentication_required' | 'isolation_unavailable' | 'enforcement_probe_required'
136
+ operatorAction: string
137
+ } & (
138
+ | { capability: 'instruction-loading' | 'guard-firing' }
139
+ | { capability: 'skill-parity'; skill: string }
140
+ )
141
+ export interface SelftestSkillReviewRecord {
142
+ skill: string
143
+ harness: 'Claude Code' | 'Codex'
144
+ status: 'evidenced' | 'human_needed'
145
+ reviewedOn: string | null
146
+ evidenceRunId: string | null
147
+ reviewers: readonly ('official-authoring' | 'harness-diagnostics')[]
148
+ checks: { trigger: 'pass'; silence: 'pass'; bodyLoad: 'pass'; tools: 'pass'; nonGoals: 'pass' } | null
149
+ operatorAction: string | null
150
+ }
151
+ export interface SelftestEvidenceBundle {
152
+ schema: 'cockpit-harness-selftest/v1'
153
+ generatedManifestSha256: string
154
+ records: readonly (SelftestEvidenceRecord | SelftestHumanNeededRecord)[]
155
+ skillInventory: readonly string[]
156
+ skillReviews: readonly SelftestSkillReviewRecord[]
157
+ }
158
+ export interface SelftestEvidenceValidationOptions extends HarnessEvidenceValidationOptions { testOnlyNow?: string }
159
+ export interface SelftestEvidenceValidationResult {
160
+ errors: readonly string[]
161
+ evidence: readonly SelftestEvidenceRecord[]
162
+ pending: readonly SelftestHumanNeededRecord[]
163
+ skillReviews: readonly SelftestSkillReviewRecord[]
164
+ }
165
+
166
+ export const PROBE_EVIDENCE_ROOT: '.cockpit/local/probes'
167
+ export function validateEvidenceManifest(
168
+ manifest: unknown,
169
+ root?: string,
170
+ options?: HarnessEvidenceValidationOptions,
171
+ ): string[]
172
+ export function validateEvidenceManifests(
173
+ manifests: readonly unknown[],
174
+ root?: string,
175
+ options?: HarnessEvidenceValidationOptions,
176
+ ): { errors: string[]; rows: HarnessEvidenceManifest[] }
177
+ export function validateSelftestEvidenceBundle(
178
+ bundle: unknown,
179
+ expectation: unknown,
180
+ root?: string,
181
+ options?: SelftestEvidenceValidationOptions,
182
+ ): SelftestEvidenceValidationResult
183
+ export function runHarnessEvidenceValidatorCli(paths: readonly string[]): number