@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,349 @@
1
+ import { sha256Hex } from '../sha256.js';
2
+ import { isAbsolute, relative, resolve } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { parseDocument } from 'yaml';
5
+ import { INHERITED_TOOL_ENVIRONMENT, snapshotJson, tryParseJson } from '../environment/contract.js';
6
+ import { reservedPathSegment, SURFACE_TARGETS } from './contract.js';
7
+ import { harnessFact } from './harness-registry.js';
8
+ const EnvironmentName = z.string().regex(/^[A-Z][A-Z0-9_]*$/).max(128);
9
+ const ServerName = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/).max(128);
10
+ const RelativePath = z.string().min(1).max(4096).refine(path => !isAbsolute(path)
11
+ && !/[\\\x00-\x1f\x7f]/.test(path) && !/^[a-z]:/i.test(path)
12
+ && path.split('/').every(part => part !== '' && part !== '.' && part !== '..' && !reservedPathSegment(part)));
13
+ const ShellToken = z.string().min(1).max(4096).refine(value => !/[\x00-\x1f\x7f]|\$\(|\$\{|`/.test(value));
14
+ const WorkingDirectory = ShellToken.refine(isAbsolute);
15
+ const Sha256 = z.string().regex(/^[a-f0-9]{64}$/);
16
+ const Source = z.discriminatedUnion('kind', [
17
+ z.object({ kind: z.literal('core'), path: RelativePath, sha256: Sha256 }).strict(),
18
+ z.object({ kind: z.literal('overlay'), path: RelativePath, sha256: Sha256 }).strict(),
19
+ z.object({ kind: z.literal('sibling'), path: RelativePath, sha256: Sha256, slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/) }).strict(),
20
+ ]);
21
+ const Argument = ShellToken.refine(value => !/^--?(?:api[-_]?key|token|password|secret|authorization|credential)(?:=|$)/i.test(value)
22
+ && !/^[a-z][a-z0-9+.-]*:\/\/[^/]*@/i.test(value));
23
+ const Command = ShellToken.refine(value => !value.startsWith('-'));
24
+ const StdioDeclaration = z.object({
25
+ name: ServerName,
26
+ command: Command,
27
+ args: z.array(Argument).max(256),
28
+ cwd: WorkingDirectory,
29
+ envNames: z.array(EnvironmentName).max(128).refine(names => new Set(names).size === names.length),
30
+ source: Source,
31
+ }).strict();
32
+ const LOOPBACK_HOSTS = /^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])$/;
33
+ const CREDENTIAL_NAME = /^(?:api[-_]?key|token|access[-_]?token|password|secret|authorization|credential)$/i;
34
+ /** Why a remote MCP URL is refused, or undefined when it is accepted. Never echoes the URL. */
35
+ function remoteMcpUrlProblem(value) {
36
+ // OpenCode expands {env:NAME} and {file:path} anywhere in opencode.jsonc, URL included, so a brace
37
+ // would let a declaration send an environment variable or a file's contents to the server.
38
+ if (/[{}]/.test(value))
39
+ return 'contains a brace, which OpenCode expands as {env:...} or {file:...} and Codex sends literally';
40
+ let url;
41
+ try {
42
+ url = new URL(value);
43
+ }
44
+ catch {
45
+ return 'is not an absolute URL';
46
+ }
47
+ if (url.username || url.password)
48
+ return 'carries credentials in its user information';
49
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && LOOPBACK_HOSTS.test(url.hostname))) {
50
+ return 'must use https, or http only to a loopback host';
51
+ }
52
+ if ([...url.searchParams.keys()].some(name => CREDENTIAL_NAME.test(name)))
53
+ return 'carries a credential-named query parameter';
54
+ return undefined;
55
+ }
56
+ // A header value is one environment reference, whole or after `Bearer ` in Authorization. Those are
57
+ // the only spellings all three tools resolve from the environment (Codex reads them as
58
+ // env_http_headers and bearer_token_env_var), so no credential literal reaches a tracked file.
59
+ const HEADER_REFERENCE = /^(Bearer )?\$\{([A-Z][A-Z0-9_]*)\}$/;
60
+ const HeaderName = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9-]*$/).max(128);
61
+ /** Why a declared header is refused, or undefined when it is an accepted environment reference. Never echoes the value. */
62
+ function remoteMcpHeaderProblem(name, value) {
63
+ if (!HeaderName.safeParse(name).success)
64
+ return 'has a name that is not an HTTP token';
65
+ const match = HEADER_REFERENCE.exec(value);
66
+ if (!match)
67
+ return 'must be exactly ${NAME}, or Bearer ${NAME} for Authorization, naming an upper-case environment variable; a literal value would commit the credential';
68
+ if (match[1] && name.toLowerCase() !== 'authorization')
69
+ return 'may use the Bearer form only in Authorization';
70
+ return undefined;
71
+ }
72
+ const Headers = z.record(z.string(), z.string().max(256))
73
+ .refine(headers => Object.keys(headers).length <= 32
74
+ && Object.entries(headers).every(([name, value]) => remoteMcpHeaderProblem(name, value) === undefined)
75
+ && new Set(Object.keys(headers).map(name => name.toLowerCase())).size === Object.keys(headers).length);
76
+ const HttpDeclaration = z.object({
77
+ name: ServerName,
78
+ transport: z.literal('http'),
79
+ url: z.string().max(2048).refine(url => remoteMcpUrlProblem(url) === undefined),
80
+ headers: Headers,
81
+ // The declared command that prints the request headers as a JSON object when the tool connects.
82
+ helper: z.object({ command: Command, args: z.array(Argument).max(64), cwd: WorkingDirectory }).strict().nullable(),
83
+ source: Source,
84
+ }).strict();
85
+ const Declaration = z.union([StdioDeclaration, HttpDeclaration]);
86
+ const Input = z.object({
87
+ cockpitDirectory: WorkingDirectory,
88
+ declarations: z.array(Declaration).max(256),
89
+ approved: z.record(ServerName, Sha256).refine(approved => Object.keys(approved).length <= 256),
90
+ targets: z.array(z.enum(SURFACE_TARGETS)).min(1).max(SURFACE_TARGETS.length).refine(targets => new Set(targets).size === targets.length),
91
+ }).strict();
92
+ function refuse(code, message) {
93
+ return { kind: 'refusal', diagnostic: { code, message } };
94
+ }
95
+ function quote(value) {
96
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
97
+ }
98
+ // Each harness starts the wrapper as `.cockpit/mcp/<id>.sh` from the directory the agent was
99
+ // launched in, so it runs only from the cockpit root (the guard hooks require the same) and the
100
+ // server's directory is relative to it: the committed bytes are the same in every clone. The `./`
101
+ // keeps CDPATH from choosing another directory and printing it into the JSON-RPC stream, and `-P`
102
+ // resolves `..` from the real root, against which the relative path was computed.
103
+ // A headers helper runs through the same kind of wrapper: Claude Code and Codex start it with
104
+ // `sh -c` from the directory the agent was launched in, and it prints the headers on stdout.
105
+ function wrapper(launch, envNames, cockpitDirectory) {
106
+ const inherited = ['PATH', ...INHERITED_TOOL_ENVIRONMENT].filter(name => !envNames.includes(name));
107
+ return [
108
+ '#!/bin/sh',
109
+ 'set -eu',
110
+ `cd -P -- ${quote(`./${relative(cockpitDirectory, launch.cwd) || '.'}`)}`,
111
+ `executable=$(command -v ${quote(launch.command)}) || { printf '%s\\n' 'MCP_EXECUTABLE_UNAVAILABLE' >&2; exit 2; }`,
112
+ '[ -f "$executable" ] && [ -x "$executable" ] || { printf \'%s\\n\' \'MCP_EXECUTABLE_UNAVAILABLE\' >&2; exit 2; }',
113
+ // `npx`, `uvx` and most npm servers are `#!/usr/bin/env node` scripts: without PATH they
114
+ // cannot start. The rest of the inherited set carries caches and locale, never a credential.
115
+ `exec /usr/bin/env -i ${inherited.map(name => `\${${name}+"${name}=$${name}"}`).join(' ')}`
116
+ + `${envNames.map(name => ` ${name}="\${${name}?required environment name ${name} is unset}"`).join('')} "$executable"${launch.args.map(arg => ` ${quote(arg)}`).join('')}`,
117
+ '',
118
+ ].join('\n');
119
+ }
120
+ /** The committed path of a server's stdio wrapper, or of its headers helper wrapper. */
121
+ function mcpWrapperPath(name, kind) {
122
+ return `.cockpit/mcp/${sha256Hex(name)}${kind === 'headers' ? '.headers' : ''}.sh`;
123
+ }
124
+ function isHttp(value) {
125
+ return 'transport' in value && value.transport === 'http';
126
+ }
127
+ function headerEnvironmentNames(headers) {
128
+ return [...new Set(Object.values(headers).map(value => HEADER_REFERENCE.exec(value)[2]))].sort();
129
+ }
130
+ // What a human reviews before approving a server: everything the wrapper runs, or the URL, the
131
+ // header references and the helper a remote server connects with. Working directories are taken
132
+ // relative to the cockpit so the digest is the same in every clone.
133
+ function identity(declaration, cockpitDirectory) {
134
+ if (isHttp(declaration)) {
135
+ const helper = declaration.helper;
136
+ return JSON.stringify({ transport: 'http', url: declaration.url,
137
+ headers: Object.entries(declaration.headers).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0),
138
+ helper: helper && { command: helper.command, args: helper.args, cwd: relative(cockpitDirectory, helper.cwd) || '.' } });
139
+ }
140
+ const stdio = declaration;
141
+ return JSON.stringify({ command: stdio.command, args: stdio.args, cwd: relative(cockpitDirectory, stdio.cwd) || '.', envNames: [...stdio.envNames].sort() });
142
+ }
143
+ /** The digest `[mcp.approved]` records for a server, so a changed declaration is no longer approved. */
144
+ export function mcpApprovalDigest(declaration, cockpitDirectory) {
145
+ return sha256Hex(identity(declaration, cockpitDirectory));
146
+ }
147
+ export function collectMcpDeclarations(input) {
148
+ const captured = snapshotJson(input);
149
+ const parsed = Input.safeParse(captured);
150
+ if (!parsed.success)
151
+ return refuse('MCP_INPUT_INVALID', 'MCP declarations, approvals and targets must be bounded canonical data');
152
+ const { approved, cockpitDirectory } = parsed.data;
153
+ const collected = new Map();
154
+ for (const declaration of parsed.data.declarations) {
155
+ const prior = collected.get(declaration.name);
156
+ if (prior && identity(prior.declaration, cockpitDirectory) !== identity(declaration, cockpitDirectory)) {
157
+ const conflict = snapshotJson({ kind: 'refusal', diagnostic: {
158
+ code: 'MCP_COMMAND_CONFLICT', server: declaration.name,
159
+ message: 'MCP server identity has divergent declarations: command, arguments, working directory or environment names for a local server; URL, header references or headers helper for a remote one',
160
+ sources: [...prior.sources, declaration.source],
161
+ } });
162
+ return conflict ? conflict
163
+ : refuse('MCP_INPUT_INVALID', 'MCP conflict exceeds the bounded data budget');
164
+ }
165
+ if (prior) {
166
+ if (!prior.sources.some(source => JSON.stringify(source) === JSON.stringify(declaration.source)))
167
+ prior.sources.push(declaration.source);
168
+ }
169
+ else
170
+ collected.set(declaration.name, { declaration, sources: [declaration.source] });
171
+ }
172
+ const digests = new Map([...collected].map(([name, { declaration }]) => [name, mcpApprovalDigest(declaration, cockpitDirectory)]));
173
+ const servers = [...collected.values()].map(({ declaration, sources }) => {
174
+ const shared = {
175
+ enabled: Object.hasOwn(approved, declaration.name) && approved[declaration.name] === digests.get(declaration.name),
176
+ sources: sources.sort((left, right) => JSON.stringify(left) < JSON.stringify(right) ? -1 : JSON.stringify(left) > JSON.stringify(right) ? 1 : 0),
177
+ };
178
+ return isHttp(declaration)
179
+ ? { name: declaration.name, transport: 'http', url: declaration.url, headers: declaration.headers, helper: declaration.helper,
180
+ envNames: headerEnvironmentNames(declaration.headers), ...shared }
181
+ : { name: declaration.name, command: declaration.command, args: declaration.args, cwd: declaration.cwd,
182
+ envNames: [...declaration.envNames].sort(), ...shared };
183
+ }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
184
+ const diagnostics = servers.filter(server => !server.enabled).map(server => ({
185
+ code: 'MCP_NOT_APPROVED', server: server.name, sources: server.sources, sha256: digests.get(server.name),
186
+ message: 'MCP server has no shared approval for its current declaration and remains disabled',
187
+ }));
188
+ for (const name of Object.keys(approved).sort())
189
+ if (!collected.has(name))
190
+ diagnostics.push({
191
+ code: 'MCP_APPROVAL_UNKNOWN', server: name, message: 'The shared approval list names an undeclared MCP server',
192
+ });
193
+ // A tool that cannot carry a remote server's credential gets nothing for it: written without
194
+ // its headers, the server would start, fail to authenticate and still read as configured.
195
+ const writes = (server, target) => {
196
+ if (!isHttp(server))
197
+ return true;
198
+ const support = harnessFact(target).remoteMcp;
199
+ return support.state !== 'unavailable' && (Object.keys(server.headers).length === 0 || support.headers)
200
+ && (server.helper === null || support.helper);
201
+ };
202
+ const targets = parsed.data.targets;
203
+ for (const server of servers) {
204
+ const skipped = targets.filter(target => !writes(server, target));
205
+ if (skipped.length > 0)
206
+ diagnostics.push({
207
+ code: 'MCP_TRANSPORT_UNAVAILABLE', server: server.name, sources: server.sources, targets: skipped,
208
+ message: `MCP server is not written for ${skipped.join(', ')}: ${skipped.map(target => harnessFact(target).remoteMcp.provenance).filter((reason, index, all) => all.indexOf(reason) === index).join('; ')}`,
209
+ });
210
+ }
211
+ const wrappers = [];
212
+ const launchers = new Map();
213
+ for (const server of servers) {
214
+ const launch = isHttp(server) ? server.helper : server;
215
+ if (launch === null || !targets.some(target => writes(server, target)))
216
+ continue;
217
+ const path = mcpWrapperPath(server.name, isHttp(server) ? 'headers' : 'server');
218
+ const bytes = wrapper(launch, isHttp(server) ? [] : server.envNames, cockpitDirectory);
219
+ wrappers.push({ path, bytes, sha256: sha256Hex(bytes) });
220
+ launchers.set(server.name, path);
221
+ }
222
+ const result = {
223
+ servers, wrappers,
224
+ harnesses: targets.map(target => ({ target, servers: servers.filter(server => writes(server, target)).map((server) => isHttp(server)
225
+ ? { name: server.name, url: server.url, headers: server.headers,
226
+ headersHelper: launchers.has(server.name) ? `/bin/sh ${launchers.get(server.name)}` : null, enabled: server.enabled }
227
+ : { name: server.name, command: '/bin/sh', args: [launchers.get(server.name)], enabled: server.enabled }) })),
228
+ };
229
+ const outcome = snapshotJson({ kind: 'complete', result, diagnostics });
230
+ return outcome ? outcome : refuse('MCP_INPUT_INVALID', 'Canonical MCP output exceeds the bounded data budget');
231
+ }
232
+ const NativeServer = z.object({
233
+ type: z.literal('stdio').optional(), command: Command,
234
+ args: z.array(Argument).max(256).default([]), cwd: RelativePath.or(z.literal('.')).optional(),
235
+ env: z.record(EnvironmentName, z.string().max(128)).default({}),
236
+ }).strict();
237
+ // Claude Code's `.mcp.json` spelling of a remote server; values are checked one by one below so a
238
+ // refusal can say which part is wrong without repeating it.
239
+ const NativeHttpServer = z.object({
240
+ type: z.literal('http'), url: z.string().max(2048),
241
+ headers: z.record(z.string(), z.string().max(256)).default({}), headersHelper: z.string().min(1).max(4096).optional(),
242
+ }).strict();
243
+ // Absence is inferred only from cockpit's neutral shape; other source forms retain generator validation.
244
+ const GeneratedServer = z.union([
245
+ NativeServer.extend({ cwd: WorkingDirectory.optional(), disabled: z.boolean().optional() }),
246
+ NativeHttpServer.extend({ http_headers_helper: z.string().min(1).max(4096).optional(), disabled: z.boolean().optional() }),
247
+ ]);
248
+ const GeneratedServers = z.record(ServerName, GeneratedServer);
249
+ const TargetServers = z.object({ mcpServers: z.record(ServerName, GeneratedServer.nullable()).optional() }).strict();
250
+ export function hasTargetMcpServers(sources, target) {
251
+ const source = sources.find(candidate => candidate.path === 'mcp.jsonc');
252
+ if (!source)
253
+ return false;
254
+ try {
255
+ const document = parseDocument(source.bytes, { uniqueKeys: true });
256
+ const root = z.record(z.string(), z.unknown()).safeParse(JSON.parse(source.bytes));
257
+ if (document.errors.length > 0 || document.warnings.length > 0 || !root.success)
258
+ return true;
259
+ const shared = GeneratedServers.safeParse(root.data.mcpServers);
260
+ const targetNames = SURFACE_TARGETS.map(candidate => harnessFact(candidate).rulesyncTarget);
261
+ if (!shared.success || Object.entries(root.data).some(([name, value]) => name !== 'mcpServers'
262
+ && (!targetNames.some(candidate => candidate === name) || !TargetServers.safeParse(value).success)))
263
+ return true;
264
+ const override = TargetServers.safeParse(root.data[harnessFact(target).rulesyncTarget] ?? {});
265
+ if (!override.success)
266
+ return true;
267
+ return Object.values({ ...shared.data, ...override.data.mcpServers }).some(server => server !== null);
268
+ }
269
+ catch {
270
+ // Unknown syntax and malformed declarations still reach Rulesync's authenticated parser.
271
+ return true;
272
+ }
273
+ }
274
+ const NativeFile = z.object({ mcpServers: z.record(ServerName, z.unknown()).refine(servers => Object.keys(servers).length <= 256) }).strict();
275
+ const ParseInput = z.object({ bytes: z.string(), source: Source, workingDirectory: WorkingDirectory }).strict();
276
+ // A helper is written as one command line in `.mcp.json`. Cockpit runs it without a shell, so each
277
+ // space-separated word must be plain: quoting, expansion and redirection belong in the helper script.
278
+ const HELPER_WORD = /^[A-Za-z0-9_@%+=:,.\/-]+$/;
279
+ function parseHttpServer(name, server, source, workingDirectory) {
280
+ const urlProblem = remoteMcpUrlProblem(server.url);
281
+ if (urlProblem)
282
+ return `MCP server ${name}: its url ${urlProblem}`;
283
+ const headerNames = Object.keys(server.headers);
284
+ if (headerNames.length > 32)
285
+ return `MCP server ${name} declares more than 32 headers`;
286
+ if (new Set(headerNames.map(header => header.toLowerCase())).size !== headerNames.length)
287
+ return `MCP server ${name} declares a header twice in different case`;
288
+ for (const [header, value] of Object.entries(server.headers)) {
289
+ const problem = remoteMcpHeaderProblem(header, value);
290
+ if (problem)
291
+ return `MCP server ${name}: header ${HeaderName.safeParse(header).success ? header : '(unnamed)'} ${problem}`;
292
+ }
293
+ let helper = null;
294
+ if (server.headersHelper !== undefined) {
295
+ const words = server.headersHelper.split(' ');
296
+ if (words.some(word => !HELPER_WORD.test(word)) || words[0].startsWith('-')) {
297
+ return `MCP server ${name}: headersHelper must be a command and arguments separated by single spaces, without quotes, variables or shell operators`;
298
+ }
299
+ helper = { command: words[0], args: words.slice(1), cwd: workingDirectory };
300
+ if (!z.array(Argument).safeParse(helper.args).success)
301
+ return `MCP server ${name}: headersHelper arguments must not carry a credential`;
302
+ }
303
+ return { name, transport: 'http', url: server.url, headers: server.headers, helper, source };
304
+ }
305
+ export function parseMcpDeclarationFile(input) {
306
+ const parsed = ParseInput.safeParse(snapshotJson(input));
307
+ if (!parsed.success)
308
+ return refuse('MCP_DECLARATION_INVALID', 'MCP source must be bounded authenticated declaration bytes');
309
+ const { bytes, source, workingDirectory } = parsed.data;
310
+ if (sha256Hex(bytes) !== source.sha256)
311
+ return refuse('MCP_DECLARATION_INVALID', 'MCP source digest does not match declaration bytes');
312
+ const value = tryParseJson(bytes);
313
+ if (value === undefined)
314
+ return refuse('MCP_DECLARATION_INVALID', 'MCP declaration is not valid JSON');
315
+ const file = NativeFile.safeParse(value);
316
+ if (!file.success)
317
+ return refuse('MCP_DECLARATION_INVALID', 'MCP declaration must be one mcpServers object of at most 256 named servers');
318
+ const declarations = [];
319
+ for (const [name, entry] of Object.entries(file.data.mcpServers)) {
320
+ const type = typeof entry === 'object' && entry !== null && 'type' in entry ? entry.type : undefined;
321
+ if (type === 'sse') {
322
+ return refuse('MCP_DECLARATION_INVALID', `MCP server ${name} uses the SSE transport, which Codex cannot connect to; declare the server's streamable HTTP endpoint with type http`);
323
+ }
324
+ if (type === 'http') {
325
+ const remote = NativeHttpServer.safeParse(entry);
326
+ if (!remote.success)
327
+ return refuse('MCP_DECLARATION_INVALID', `MCP server ${name} is a remote server with fields other than type, url, headers and headersHelper`);
328
+ const declaration = parseHttpServer(name, remote.data, source, workingDirectory);
329
+ if (typeof declaration === 'string')
330
+ return refuse('MCP_DECLARATION_INVALID', declaration);
331
+ declarations.push(declaration);
332
+ continue;
333
+ }
334
+ const local = NativeServer.safeParse(entry);
335
+ if (!local.success)
336
+ return refuse('MCP_DECLARATION_INVALID', 'MCP declaration contains unsupported command, environment or transport fields');
337
+ const server = local.data;
338
+ if (Object.entries(server.env).some(([key, reference]) => reference !== `\${${key}}`)) {
339
+ return refuse('MCP_DECLARATION_INVALID', 'MCP environment entries must reference their same declared environment name');
340
+ }
341
+ const cwd = resolve(workingDirectory, server.cwd ?? '.');
342
+ const offset = relative(workingDirectory, cwd);
343
+ if (offset === '..' || offset.startsWith('../') || isAbsolute(offset))
344
+ return refuse('MCP_DECLARATION_INVALID', 'MCP working directory escapes its declared sibling');
345
+ declarations.push({ name, command: server.command, args: server.args, cwd, envNames: Object.keys(server.env).sort(), source });
346
+ }
347
+ const captured = snapshotJson({ kind: 'complete', declarations });
348
+ return captured ? captured : refuse('MCP_DECLARATION_INVALID', 'MCP declarations exceed the bounded data budget');
349
+ }
@@ -0,0 +1,30 @@
1
+ import { type SurfaceDeclaration as SurfaceDeclarationValue } from '../schema.js';
2
+ import type { SurfaceFeature } from './contract.js';
3
+ export interface ActiveSurfaceOverride {
4
+ readonly name: string;
5
+ readonly feature: SurfaceFeature;
6
+ readonly originalSha256: string;
7
+ readonly recordedSha256: string;
8
+ readonly overlaySource: string;
9
+ }
10
+ export interface SurfaceUpgradeFinding {
11
+ readonly code: 'OVERRIDDEN_CORE_CHANGED';
12
+ readonly name: string;
13
+ readonly recordedSha256: string;
14
+ readonly currentSha256: string;
15
+ readonly overlaySource: string;
16
+ }
17
+ export interface SurfaceMergeComplete {
18
+ readonly kind: 'complete';
19
+ readonly surfaces: readonly SurfaceDeclarationValue[];
20
+ readonly overrides: readonly ActiveSurfaceOverride[];
21
+ readonly findings: readonly SurfaceUpgradeFinding[];
22
+ }
23
+ export interface SurfaceMergeRefusal {
24
+ readonly kind: 'refusal';
25
+ readonly code: 'SURFACE_INPUT_INVALID' | 'SURFACE_NAME_DUPLICATE' | 'SURFACE_KIND_AMBIGUOUS' | 'SURFACE_OVERRIDE_INVALID' | 'SURFACE_PATH_CONFLICT';
26
+ readonly message: string;
27
+ }
28
+ export type SurfaceMergeResult = SurfaceMergeComplete | SurfaceMergeRefusal;
29
+ export declare function surfaceDeclarationSha256(declaration: SurfaceDeclarationValue): string;
30
+ export declare function mergeSurfaceDeclarations(coreInput: unknown, overlayInput: unknown): SurfaceMergeResult;
@@ -0,0 +1,105 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { snapshotJson } from '../environment/contract.js';
3
+ import { SurfaceDeclaration, SurfaceOverlay } from '../schema.js';
4
+ function immutable(value) {
5
+ return snapshotJson(value);
6
+ }
7
+ function isObject(value) {
8
+ return value !== undefined && value !== null && typeof value === 'object' && !Array.isArray(value);
9
+ }
10
+ function refusal(code, message) {
11
+ return immutable({ kind: 'refusal', code, message });
12
+ }
13
+ function canonicalDeclaration(declaration) {
14
+ return {
15
+ name: declaration.name,
16
+ feature: declaration.feature,
17
+ sources: declaration.sources.map((source) => ({ path: source.path, bytes: source.bytes })),
18
+ source: declaration.source,
19
+ ...(declaration.replaces ? { replaces: declaration.replaces } : {}),
20
+ };
21
+ }
22
+ export function surfaceDeclarationSha256(declaration) {
23
+ return createHash('sha256').update(JSON.stringify(canonicalDeclaration(declaration))).digest('hex');
24
+ }
25
+ function duplicateName(declarations) {
26
+ const names = new Set();
27
+ for (const declaration of declarations) {
28
+ if (names.has(declaration.name))
29
+ return declaration.name;
30
+ names.add(declaration.name);
31
+ }
32
+ return undefined;
33
+ }
34
+ function compareName(left, right) {
35
+ return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
36
+ }
37
+ export function mergeSurfaceDeclarations(coreInput, overlayInput) {
38
+ const captured = snapshotJson({ core: coreInput, overlays: overlayInput });
39
+ if (!isObject(captured)) {
40
+ return refusal('SURFACE_INPUT_INVALID', 'core and overlay declarations must be bounded immutable data');
41
+ }
42
+ const coreResult = SurfaceDeclaration.array().safeParse(captured.core);
43
+ const overlayResult = SurfaceOverlay.array().safeParse(captured.overlays);
44
+ if (!coreResult.success || !overlayResult.success) {
45
+ return refusal('SURFACE_INPUT_INVALID', 'core or overlay declarations are invalid');
46
+ }
47
+ const core = coreResult.data;
48
+ const overlays = overlayResult.data;
49
+ const repeated = duplicateName(core) ?? duplicateName(overlays);
50
+ if (repeated)
51
+ return refusal('SURFACE_NAME_DUPLICATE', `surface name "${repeated}" is declared more than once`);
52
+ if (core.some((declaration) => declaration.replaces)) {
53
+ return refusal('SURFACE_OVERRIDE_INVALID', 'core declarations cannot replace another surface');
54
+ }
55
+ const resolved = new Map(core.map((declaration) => [declaration.name, declaration]));
56
+ const overrides = [];
57
+ const findings = [];
58
+ for (const overlay of overlays) {
59
+ const original = resolved.get(overlay.name);
60
+ if (!original) {
61
+ if (overlay.replaces)
62
+ return refusal('SURFACE_OVERRIDE_INVALID', `override "${overlay.name}" has no core original`);
63
+ resolved.set(overlay.name, overlay);
64
+ continue;
65
+ }
66
+ if (original.feature !== overlay.feature) {
67
+ return refusal('SURFACE_KIND_AMBIGUOUS', `surface "${overlay.name}" changes feature from ${original.feature} to ${overlay.feature}`);
68
+ }
69
+ if (!overlay.replaces || overlay.replaces.name !== overlay.name) {
70
+ return refusal('SURFACE_OVERRIDE_INVALID', `surface "${overlay.name}" collides with core without an explicit replacement`);
71
+ }
72
+ const originalSha256 = surfaceDeclarationSha256(original);
73
+ overrides.push({
74
+ name: overlay.name,
75
+ feature: overlay.feature,
76
+ originalSha256,
77
+ recordedSha256: overlay.replaces.sha256,
78
+ overlaySource: overlay.source,
79
+ });
80
+ if (originalSha256 !== overlay.replaces.sha256) {
81
+ findings.push({
82
+ code: 'OVERRIDDEN_CORE_CHANGED',
83
+ name: overlay.name,
84
+ recordedSha256: overlay.replaces.sha256,
85
+ currentSha256: originalSha256,
86
+ overlaySource: overlay.source,
87
+ });
88
+ }
89
+ resolved.set(overlay.name, overlay);
90
+ }
91
+ const paths = new Map();
92
+ const surfaces = [...resolved.values()];
93
+ for (const declaration of surfaces) {
94
+ for (const source of declaration.sources) {
95
+ const owner = paths.get(source.path);
96
+ if (owner)
97
+ return refusal('SURFACE_PATH_CONFLICT', `surface path "${source.path}" is owned by both "${owner}" and "${declaration.name}"`);
98
+ paths.set(source.path, declaration.name);
99
+ }
100
+ }
101
+ surfaces.sort(compareName);
102
+ overrides.sort(compareName);
103
+ findings.sort(compareName);
104
+ return immutable({ kind: 'complete', surfaces, overrides, findings });
105
+ }