@b00dea/cockpit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +22 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +74 -4
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,809 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { sha256Hex } from '../sha256.js';
3
+ import { constants } from 'node:fs';
4
+ import { chmod, lstat, mkdir, mkdtemp, open, realpath, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { authenticatedExecutionOutcome, executeProbe } from '../environment-exec/execute.js';
9
+ import { libexecProgram } from '../environment-exec/libexec-program.js';
10
+ import { ENVIRONMENT_CODES, snapshotExecutionStep, snapshotGeneratedJson, RefusalError, errorCause, tryParseJson, hasErrorCode } from '../environment/contract.js';
11
+ import { bundleSurfaceFiles, captureSurfaceBundleBatch, captureSurfaceBatch, SURFACE_FILE_MAX_BYTES, SURFACE_OUTPUT_MAX_BYTES, SURFACE_OUTPUT_MAX_FILES, } from './contract.js';
12
+ import { harnessFact } from './harness-registry.js';
13
+ import { hasTargetMcpServers } from './mcp.js';
14
+ // The bundled Rulesync release, recorded as every generated surface's provenance. Written by
15
+ // npm run bundle:rulesync from devDependencies.rulesync in package.json; --check refuses a drift.
16
+ export const RULESYNC_VERSION = '43.4.2';
17
+ const OPERATION_TIMEOUT_MS = 30_000;
18
+ const CLEANUP_TIMEOUT_MS = 2_000;
19
+ const MAX_NODE_BYTES = 536_870_912;
20
+ const MAX_GENERATOR_BUNDLE_BYTES = 4_194_304;
21
+ const MAX_WORKER_STDOUT_BYTES = 8_388_608;
22
+ const MAX_WORKER_STDERR_BYTES = 65_536;
23
+ const MAX_WARNING_COUNT = 16;
24
+ const MAX_WARNING_BYTES = 4_096;
25
+ const MAX_WARNING_TOTAL_BYTES = 16_384;
26
+ const SHA256 = /^[a-f0-9]{64}$/;
27
+ const FEATURES = new Set(['rules', 'mcp', 'subagents', 'skills', 'hooks', 'permissions']);
28
+ const EMPTY_FILES = Object.freeze([]);
29
+ const EMPTY_DIAGNOSTICS = Object.freeze([]);
30
+ const EMPTY_WARNINGS = Object.freeze([]);
31
+ // This digest binds the checked-in, self-contained generator asset. The build-time package
32
+ // tree is deliberately absent from the runtime authority boundary. npm run bundle:rulesync
33
+ // writes it with the bundle.
34
+ const PINNED_GENERATOR_BUNDLE_SHA256 = 'ea7032a125b95b3d31643861dedbf86c856cb01306c3aa05cf5a5e99386e36a6';
35
+ // The worker programs enforce these bounds; they receive them in each request so the limits keep
36
+ // one owner here.
37
+ const WORKER_OUTPUT_LIMITS = Object.freeze({
38
+ fileMaxBytes: SURFACE_FILE_MAX_BYTES,
39
+ outputMaxFiles: SURFACE_OUTPUT_MAX_FILES,
40
+ outputMaxBytes: SURFACE_OUTPUT_MAX_BYTES,
41
+ stdoutMaxBytes: MAX_WORKER_STDOUT_BYTES,
42
+ });
43
+ const GENERATOR_LIMITS = Object.freeze({
44
+ ...WORKER_OUTPUT_LIMITS,
45
+ stderrMaxBytes: MAX_WORKER_STDERR_BYTES,
46
+ warningCount: MAX_WARNING_COUNT,
47
+ warningBytes: MAX_WARNING_BYTES,
48
+ warningTotalBytes: MAX_WARNING_TOTAL_BYTES,
49
+ });
50
+ function diagnostic(code, message, cause, target, stderr) {
51
+ return {
52
+ severity: 'error',
53
+ code,
54
+ message,
55
+ cause,
56
+ ...(target === undefined ? {} : { target }),
57
+ ...(stderr === undefined ? {} : { stderr }),
58
+ };
59
+ }
60
+ function refuse(item) {
61
+ return { kind: 'refusal', status: 2, diagnostics: [item], files: EMPTY_FILES };
62
+ }
63
+ function unavailable(item) {
64
+ return { kind: 'unavailable', status: 3, diagnostics: [item], files: EMPTY_FILES };
65
+ }
66
+ function complete(files, warnings) {
67
+ return {
68
+ kind: 'complete',
69
+ status: 0,
70
+ diagnostics: EMPTY_DIAGNOSTICS,
71
+ mechanism: { package: 'rulesync', version: RULESYNC_VERSION, role: 'spelling' },
72
+ files: files.map(file => ({ ...file })),
73
+ warnings: warnings.length === 0 ? EMPTY_WARNINGS : warnings.map(warning => ({ ...warning })),
74
+ };
75
+ }
76
+ function completeBundle(format, files) {
77
+ return {
78
+ kind: 'complete',
79
+ status: 0,
80
+ diagnostics: EMPTY_DIAGNOSTICS,
81
+ format,
82
+ mechanism: { package: 'rulesync', version: RULESYNC_VERSION, role: 'packaging' },
83
+ files: files.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 })),
84
+ };
85
+ }
86
+ // Each refusal names what differed so the unavailable diagnostic can say why authority failed.
87
+ async function digestFile(path, maxBytes, executable = false) {
88
+ let handle;
89
+ try {
90
+ if (constants.O_NOFOLLOW === undefined)
91
+ throw new RefusalError('this platform cannot open files without following symbolic links');
92
+ const before = await lstat(path);
93
+ if (!before.isFile() || before.isSymbolicLink())
94
+ throw new RefusalError(`${path} is not a regular file`);
95
+ if (before.size > maxBytes)
96
+ throw new RefusalError(`${path} exceeds ${maxBytes} bytes`);
97
+ if (executable && (before.mode & 0o111) === 0)
98
+ throw new RefusalError(`${path} is not executable`);
99
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
100
+ const opened = await handle.stat();
101
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size > maxBytes) {
102
+ throw new RefusalError(`${path} changed while it was opened`);
103
+ }
104
+ const hash = createHash('sha256');
105
+ const chunk = Buffer.allocUnsafe(Math.min(maxBytes + 1, 1_048_576));
106
+ let total = 0;
107
+ while (true) {
108
+ const { bytesRead } = await handle.read(chunk, 0, Math.min(chunk.length, maxBytes - total + 1), null);
109
+ if (bytesRead === 0)
110
+ break;
111
+ total += bytesRead;
112
+ if (total > maxBytes)
113
+ throw new RefusalError(`${path} grew past ${maxBytes} bytes while it was read`);
114
+ hash.update(chunk.subarray(0, bytesRead));
115
+ }
116
+ const after = await handle.stat();
117
+ if (total !== opened.size || after.size !== opened.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) {
118
+ throw new RefusalError(`${path} changed while it was read`);
119
+ }
120
+ return hash.digest('hex');
121
+ }
122
+ finally {
123
+ await handle?.close();
124
+ }
125
+ }
126
+ async function nodeAuthority() {
127
+ const path = await realpath(process.execPath);
128
+ return { path, sha256: await digestFile(path, MAX_NODE_BYTES, true) };
129
+ }
130
+ async function bundleAuthority() {
131
+ const path = fileURLToPath(new URL('../../libexec/rulesync-generator.cjs', import.meta.url));
132
+ if (await realpath(path) !== path)
133
+ throw new RefusalError(`${path} is reached through a symbolic link`);
134
+ const digest = await digestFile(path, MAX_GENERATOR_BUNDLE_BYTES);
135
+ if (digest !== PINNED_GENERATOR_BUNDLE_SHA256)
136
+ throw new RefusalError(`${path} differs from the pinned bundle digest`);
137
+ return { path, sha256: digest, maxBytes: MAX_GENERATOR_BUNDLE_BYTES };
138
+ }
139
+ function surfacePrograms() {
140
+ return {
141
+ generate: libexecProgram('surface-generator-worker.cjs'),
142
+ read: libexecProgram('surface-output-reader.cjs'),
143
+ cleanup: libexecProgram('surface-scratch-cleanup.cjs'),
144
+ };
145
+ }
146
+ function step(node, id, cwd, program, request, deadline, stdoutLimitBytes = MAX_WORKER_STDOUT_BYTES, authenticatedFiles = []) {
147
+ const remaining = deadline - Date.now();
148
+ if (remaining <= 0 || Buffer.byteLength(request) > 1_048_576)
149
+ return undefined;
150
+ return snapshotExecutionStep({
151
+ id,
152
+ // '--' ends Node's own options, so the program path can never be read as one.
153
+ argv: [node.path, '--', program.path, request, ...authenticatedFiles.map(file => file.path)],
154
+ argv0: node.path,
155
+ cwd,
156
+ env: { NODE_ENV: 'production' },
157
+ timeoutMs: Math.min(OPERATION_TIMEOUT_MS, remaining),
158
+ spawnNotAfter: new Date(deadline).toISOString(),
159
+ stdoutLimitBytes,
160
+ stderrLimitBytes: MAX_WORKER_STDERR_BYTES,
161
+ paths: [cwd],
162
+ ports: [],
163
+ labels: { operation: 'surface-generation' },
164
+ expected: [],
165
+ consumedFiles: [
166
+ {
167
+ path: node.path,
168
+ sha256: node.sha256,
169
+ maxBytes: MAX_NODE_BYTES,
170
+ argvIndexes: [0],
171
+ executable: true,
172
+ executionMode: 'verified-path',
173
+ },
174
+ { path: program.path, sha256: program.sha256, maxBytes: program.maxBytes, argvIndexes: [2], executable: false },
175
+ ...authenticatedFiles.map((file, index) => ({
176
+ path: file.path,
177
+ sha256: file.sha256,
178
+ maxBytes: file.maxBytes,
179
+ argvIndexes: [4 + index],
180
+ executable: false,
181
+ })),
182
+ ],
183
+ filesystemChecks: [
184
+ { path: cwd, expected: 'directory' },
185
+ ],
186
+ compensation: [],
187
+ });
188
+ }
189
+ function outputText(bytes) {
190
+ try {
191
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
192
+ }
193
+ catch {
194
+ // undecodable bytes are malformed input the caller refuses.
195
+ return undefined;
196
+ }
197
+ }
198
+ function executionFailure(outcome, target) {
199
+ const stderr = outputText(outcome.stderr);
200
+ if (outcome.kind === 'refusal' && outcome.code === ENVIRONMENT_CODES.EXECUTION_TERMINATION_UNCONFIRMED) {
201
+ return diagnostic(outcome.code, 'surface worker termination could not be confirmed', 'signal', target, stderr);
202
+ }
203
+ if (outcome.kind === 'refusal' && outcome.code === ENVIRONMENT_CODES.EXECUTION_TIMEOUT) {
204
+ return diagnostic('SURFACE_WORKER_TIMEOUT', 'surface generator worker exceeded the batch deadline', 'timeout', target, stderr);
205
+ }
206
+ if (outcome.execution.signal !== null) {
207
+ return diagnostic('SURFACE_WORKER_SIGNAL', `surface generator worker ended by ${outcome.execution.signal}`, 'signal', target, stderr);
208
+ }
209
+ return diagnostic('SURFACE_WORKER_NONZERO', 'surface generator worker did not complete successfully', 'nonzero', target, stderr);
210
+ }
211
+ function ownRecord(value) {
212
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
213
+ && Object.getPrototypeOf(value) === Object.prototype ? value : undefined;
214
+ }
215
+ function permissionRulesForTarget(sources, target) {
216
+ const source = sources.find(candidate => candidate.path === 'permissions.jsonc');
217
+ if (!source)
218
+ return false;
219
+ try {
220
+ const record = ownRecord(JSON.parse(source.bytes));
221
+ const sections = [ownRecord(record?.permission), ownRecord(ownRecord(record?.[target])?.permission)];
222
+ return sections.some(permission => permission !== undefined && Object.values(permission).some(value => {
223
+ const rules = ownRecord(value);
224
+ return rules !== undefined && Object.keys(rules).length > 0;
225
+ }));
226
+ }
227
+ catch {
228
+ // Retain malformed input so Rulesync's authenticated parser error is returned to the caller.
229
+ return true;
230
+ }
231
+ }
232
+ function stringArray(value) {
233
+ return Array.isArray(value) && value.every(item => typeof item === 'string') ? value : undefined;
234
+ }
235
+ function workerFile(value) {
236
+ const record = ownRecord(value);
237
+ const keys = record ? Object.keys(record).sort() : [];
238
+ return record && keys.join(',') === 'bytes,path,sha256'
239
+ && typeof record.path === 'string' && typeof record.bytes === 'number' && Number.isSafeInteger(record.bytes)
240
+ && record.bytes >= 0 && record.bytes <= SURFACE_FILE_MAX_BYTES && typeof record.sha256 === 'string' && SHA256.test(record.sha256)
241
+ ? { path: record.path, bytes: record.bytes, sha256: record.sha256 } : undefined;
242
+ }
243
+ function workerWarnings(value, target) {
244
+ if (!Array.isArray(value) || value.length > MAX_WARNING_COUNT)
245
+ return undefined;
246
+ if (target !== 'claudecode')
247
+ return value.length === 0 ? EMPTY_WARNINGS : undefined;
248
+ const warnings = [];
249
+ let total = 0;
250
+ for (const item of value) {
251
+ const record = ownRecord(item);
252
+ if (!record || Object.keys(record).sort().join(',') !== 'code,message,path,severity,target'
253
+ || record.severity !== 'warning' || record.code !== 'SURFACE_GENERATOR_TRUST_WARNING'
254
+ || record.target !== target || record.path !== '.claude/settings.json' || typeof record.message !== 'string')
255
+ return undefined;
256
+ const bytes = Buffer.byteLength(record.message);
257
+ total += bytes;
258
+ if (bytes > MAX_WARNING_BYTES || total > MAX_WARNING_TOTAL_BYTES)
259
+ return undefined;
260
+ warnings.push({
261
+ severity: 'warning',
262
+ code: 'SURFACE_GENERATOR_TRUST_WARNING',
263
+ target,
264
+ path: '.claude/settings.json',
265
+ message: record.message,
266
+ });
267
+ }
268
+ return warnings;
269
+ }
270
+ function parseWorkerReceipt(bytes, target, requested) {
271
+ const text = outputText(bytes);
272
+ const record = text === undefined ? undefined : ownRecord(tryParseJson(text));
273
+ if (!record || Object.keys(record).sort().join(',') !== 'actualFiles,features,reportedPaths,target,version,warnings'
274
+ || record.version !== 1 || record.target !== target || !Array.isArray(record.features) || record.features.length !== requested.length
275
+ || !Array.isArray(record.actualFiles) || record.actualFiles.length > SURFACE_OUTPUT_MAX_FILES)
276
+ return undefined;
277
+ const reportedPaths = stringArray(record.reportedPaths);
278
+ const actualFiles = record.actualFiles.map(workerFile);
279
+ const warnings = workerWarnings(record.warnings, target);
280
+ if (!reportedPaths || !warnings || new Set(reportedPaths).size !== reportedPaths.length || actualFiles.some(file => !file))
281
+ return undefined;
282
+ const features = [];
283
+ for (const [index, value] of record.features.entries()) {
284
+ const feature = ownRecord(value);
285
+ if (!feature || Object.keys(feature).sort().join(',') !== 'count,feature,paths' || feature.feature !== requested[index]
286
+ || !FEATURES.has(feature.feature) || !Number.isSafeInteger(feature.count) || feature.count < 0)
287
+ return undefined;
288
+ const paths = stringArray(feature.paths);
289
+ if (!paths || new Set(paths).size !== paths.length)
290
+ return undefined;
291
+ features.push({ feature: feature.feature, count: feature.count, paths });
292
+ }
293
+ return {
294
+ version: 1,
295
+ target,
296
+ features,
297
+ reportedPaths: [...reportedPaths],
298
+ actualFiles: actualFiles,
299
+ warnings,
300
+ };
301
+ }
302
+ function parseReadReceipt(bytes) {
303
+ const text = outputText(bytes);
304
+ const record = text === undefined ? undefined : ownRecord(tryParseJson(text));
305
+ if (!record || Object.keys(record).sort().join(',') !== 'files,version' || record.version !== 1
306
+ || !Array.isArray(record.files) || record.files.length > SURFACE_OUTPUT_MAX_FILES)
307
+ return undefined;
308
+ const files = [];
309
+ let total = 0;
310
+ for (const value of record.files) {
311
+ const item = ownRecord(value);
312
+ if (!item || Object.keys(item).sort().join(',') !== 'bytes,bytesBase64,path,sha256' || typeof item.bytesBase64 !== 'string')
313
+ return undefined;
314
+ const file = workerFile({ path: item.path, bytes: item.bytes, sha256: item.sha256 });
315
+ if (!file)
316
+ return undefined;
317
+ const decoded = Buffer.from(item.bytesBase64, 'base64');
318
+ total += decoded.byteLength;
319
+ if (decoded.byteLength !== file.bytes || total > SURFACE_OUTPUT_MAX_BYTES || sha256Hex(decoded) !== file.sha256)
320
+ return undefined;
321
+ files.push({ ...file, bytesBase64: item.bytesBase64 });
322
+ }
323
+ return files;
324
+ }
325
+ function samePaths(actual, expected) {
326
+ if (actual.length !== expected.length)
327
+ return false;
328
+ const sortedActual = [...actual].sort((left, right) => left.localeCompare(right));
329
+ const sortedExpected = [...expected].sort((left, right) => left.localeCompare(right));
330
+ return sortedActual.every((path, index) => path === sortedExpected[index]);
331
+ }
332
+ function sameRenderedFiles(left, right) {
333
+ if (left.length !== right.length)
334
+ return false;
335
+ const key = (file) => `${file.target}\0${file.path}`;
336
+ const sortedLeft = [...left].sort((a, b) => key(a).localeCompare(key(b)));
337
+ const sortedRight = [...right].sort((a, b) => key(a).localeCompare(key(b)));
338
+ return sortedLeft.every((file, index) => {
339
+ const other = sortedRight[index];
340
+ return other !== undefined && file.target === other.target && file.path === other.path
341
+ && file.bytes === other.bytes && file.sha256 === other.sha256;
342
+ });
343
+ }
344
+ function sameWarnings(left, right) {
345
+ return left.length === right.length && left.every((warning, index) => {
346
+ const other = right[index];
347
+ return other !== undefined && warning.severity === other.severity && warning.code === other.code
348
+ && warning.target === other.target && warning.path === other.path && warning.message === other.message;
349
+ });
350
+ }
351
+ async function writeSources(root, batch) {
352
+ for (const source of batch.sources) {
353
+ const target = resolve(root, source.path);
354
+ const offset = relative(root, target);
355
+ if (offset === '' || offset === '..' || offset.startsWith(`..${sep}`) || isAbsolute(offset))
356
+ throw new RefusalError('source path escaped staging');
357
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
358
+ await writeFile(target, source.bytes, { mode: 0o600, flag: 'wx' });
359
+ }
360
+ }
361
+ async function directoryIdentity(path) {
362
+ try {
363
+ const status = await lstat(path);
364
+ return status.isDirectory() && !status.isSymbolicLink()
365
+ ? { dev: status.dev, ino: status.ino } : undefined;
366
+ }
367
+ catch {
368
+ // an unobservable directory has no identity; the caller refuses.
369
+ return undefined;
370
+ }
371
+ }
372
+ function canonicalReceipt(bytes) {
373
+ const text = outputText(bytes);
374
+ if (text === undefined || !text.endsWith('\n'))
375
+ return false;
376
+ const value = tryParseJson(text);
377
+ return value !== undefined && `${JSON.stringify(value)}\n` === text;
378
+ }
379
+ async function readPrivateReceipt(path, summary) {
380
+ let handle;
381
+ try {
382
+ if (constants.O_NOFOLLOW === undefined || summary.bytes < 1 || summary.bytes > MAX_WORKER_STDOUT_BYTES)
383
+ return undefined;
384
+ const before = await lstat(path);
385
+ if (!before.isFile() || before.isSymbolicLink() || before.size !== summary.bytes)
386
+ return undefined;
387
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
388
+ const opened = await handle.stat();
389
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== summary.bytes)
390
+ return undefined;
391
+ const bytes = Buffer.allocUnsafe(summary.bytes);
392
+ let offset = 0;
393
+ while (offset < bytes.length) {
394
+ const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, null);
395
+ if (bytesRead === 0)
396
+ return undefined;
397
+ offset += bytesRead;
398
+ }
399
+ const after = await handle.stat();
400
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
401
+ || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs
402
+ || sha256Hex(bytes) !== summary.sha256 || !canonicalReceipt(bytes))
403
+ return undefined;
404
+ return bytes;
405
+ }
406
+ catch {
407
+ // an unavailable authority is refused by the caller.
408
+ return undefined;
409
+ }
410
+ finally {
411
+ try {
412
+ await handle?.close();
413
+ }
414
+ catch { /* an unavailable authority is refused by the caller. */
415
+ return undefined;
416
+ }
417
+ }
418
+ }
419
+ function receiptSummaries(bytes, jobs) {
420
+ const text = outputText(bytes);
421
+ const record = text === undefined ? undefined : ownRecord(tryParseJson(text));
422
+ if (!record || Object.keys(record).sort().join(',') !== 'jobs,version' || record.version !== 1
423
+ || !Array.isArray(record.jobs) || record.jobs.length !== jobs.length)
424
+ return undefined;
425
+ const summaries = new Map();
426
+ for (let index = 0; index < jobs.length; index += 1) {
427
+ const expected = jobs[index];
428
+ const row = ownRecord(record.jobs[index]);
429
+ if (!row || row.id !== expected.id || row.target !== expected.generatorTarget || summaries.has(expected.id)
430
+ || row.kind !== 'complete' && row.kind !== 'error')
431
+ return undefined;
432
+ if (row.kind === 'error') {
433
+ if (Object.keys(row).sort().join(',') !== 'cause,id,kind,message,target'
434
+ || row.cause !== 'nonzero' && row.cause !== 'stderr' && row.cause !== 'artifact'
435
+ || typeof row.message !== 'string'
436
+ || Buffer.byteLength(row.message) > 4_096)
437
+ return undefined;
438
+ summaries.set(expected.id, row.cause === 'stderr'
439
+ ? diagnostic('SURFACE_WORKER_STDERR', 'surface generator emitted a diagnostic; partial output was refused', 'stderr', expected.generatorTarget, row.message)
440
+ : row.cause === 'artifact'
441
+ ? diagnostic('SURFACE_ARTIFACT_INVALID', 'surface output could not be authenticated', 'artifact', expected.generatorTarget, row.message)
442
+ : diagnostic('SURFACE_WORKER_NONZERO', 'surface generator worker did not complete successfully', 'nonzero', expected.generatorTarget, row.message));
443
+ continue;
444
+ }
445
+ if (Object.keys(row).sort().join(',') !== 'bytes,id,kind,sha256,target'
446
+ || !Number.isSafeInteger(row.bytes) || row.bytes < 1 || row.bytes > MAX_WORKER_STDOUT_BYTES
447
+ || typeof row.sha256 !== 'string' || !SHA256.test(row.sha256))
448
+ return undefined;
449
+ summaries.set(expected.id, {
450
+ id: expected.id,
451
+ target: expected.generatorTarget,
452
+ bytes: row.bytes,
453
+ sha256: row.sha256,
454
+ });
455
+ }
456
+ return summaries;
457
+ }
458
+ async function stageEstatePass(root, jobs) {
459
+ const staged = [];
460
+ for (const job of jobs) {
461
+ const cwd = join(root, job.id);
462
+ const inputRoot = join(cwd, 'input');
463
+ const outputRoot = join(cwd, 'output');
464
+ await mkdir(inputRoot, { recursive: true, mode: 0o700 });
465
+ await mkdir(outputRoot, { recursive: true, mode: 0o700 });
466
+ await chmod(cwd, 0o700);
467
+ await writeFile(join(cwd, 'rulesync.jsonc'), '{}\n', { mode: 0o600, flag: 'wx' });
468
+ await writeSources(inputRoot, job.batch);
469
+ const outputIdentity = await directoryIdentity(outputRoot);
470
+ if (!outputIdentity)
471
+ return undefined;
472
+ staged.push({
473
+ ...job,
474
+ cwd,
475
+ inputRoot,
476
+ outputRoot,
477
+ outputIdentity,
478
+ generatorReceipt: join(cwd, 'generator-receipt.json'),
479
+ readerReceipt: join(cwd, 'reader-receipt.json'),
480
+ });
481
+ }
482
+ return staged;
483
+ }
484
+ async function runEstatePass(pass, root, jobs, node, bundle, programs, deadline) {
485
+ const failures = (item) => ({
486
+ ...(item.code === ENVIRONMENT_CODES.EXECUTION_TERMINATION_UNCONFIRMED ? { preserveScratch: true } : {}),
487
+ jobs: new Map(jobs.map(job => [job.id, { ...item, target: job.generatorTarget }])),
488
+ });
489
+ const identity = await directoryIdentity(root);
490
+ if (!identity)
491
+ return failures(diagnostic('SURFACE_ARTIFACT_INVALID', 'surface pass root is not a regular directory', 'artifact', pass));
492
+ const generatorJobs = await Promise.all(jobs.map(async (job) => ({ ...job, cwdIdentity: await directoryIdentity(job.cwd) })));
493
+ if (generatorJobs.some(job => !job.cwdIdentity)) {
494
+ return failures(diagnostic('SURFACE_ARTIFACT_INVALID', 'surface job root is not a regular directory', 'artifact', pass));
495
+ }
496
+ const generatorRequest = JSON.stringify({
497
+ version: 1,
498
+ passRoot: root,
499
+ identity,
500
+ bundleSha256: bundle.sha256,
501
+ bundleMaxBytes: bundle.maxBytes,
502
+ limits: GENERATOR_LIMITS,
503
+ jobs: generatorJobs.map(job => ({
504
+ id: job.id,
505
+ target: job.generatorTarget,
506
+ cwd: job.cwd,
507
+ cwdIdentity: job.cwdIdentity,
508
+ inputRoot: job.inputRoot,
509
+ outputRoot: job.outputRoot,
510
+ identity: job.outputIdentity,
511
+ features: job.features,
512
+ warningSourceSha256: (() => {
513
+ const source = job.batch.sources.find(item => item.path === 'permissions.jsonc');
514
+ return source ? sha256Hex(source.bytes) : null;
515
+ })(),
516
+ receiptOutput: job.generatorReceipt,
517
+ })),
518
+ });
519
+ const generationStep = step(node, `surface-estate-generate-${pass}-${randomBytes(8).toString('hex')}`, root, programs.generate, generatorRequest, deadline, MAX_WORKER_STDOUT_BYTES, [bundle]);
520
+ if (!generationStep)
521
+ return failures(diagnostic('SURFACE_WORKER_TIMEOUT', 'surface generator batch deadline expired before spawn', 'timeout', pass));
522
+ const generation = authenticatedExecutionOutcome(await executeProbe(generationStep), generationStep);
523
+ if (!generation || generation.kind !== 'complete') {
524
+ return failures(generation ? executionFailure(generation, pass)
525
+ : diagnostic('SURFACE_WORKER_OUTPUT', 'surface generator execution was not authenticated', 'worker-output', pass));
526
+ }
527
+ if (generation.stderr.byteLength > 0) {
528
+ return failures(diagnostic('SURFACE_WORKER_STDERR', 'surface generator emitted a diagnostic; partial output was refused', 'stderr', pass, outputText(generation.stderr) ?? 'stderr was not valid UTF-8'));
529
+ }
530
+ const generated = receiptSummaries(generation.stdout, jobs);
531
+ if (!generated)
532
+ return failures(diagnostic('SURFACE_WORKER_OUTPUT', 'surface generator returned an invalid estate receipt', 'worker-output', pass));
533
+ const generatedReceipts = new Map();
534
+ const generatedFailures = new Map();
535
+ for (const job of jobs) {
536
+ const summary = generated.get(job.id);
537
+ if (!summary || 'severity' in summary) {
538
+ generatedFailures.set(job.id, summary && 'severity' in summary ? summary
539
+ : diagnostic('SURFACE_WORKER_OUTPUT', 'surface generator omitted an estate job receipt', 'worker-output', job.generatorTarget));
540
+ continue;
541
+ }
542
+ const bytes = await readPrivateReceipt(job.generatorReceipt, summary);
543
+ const receipt = bytes ? parseWorkerReceipt(bytes, job.generatorTarget, job.features) : undefined;
544
+ if (!receipt) {
545
+ generatedFailures.set(job.id, diagnostic('SURFACE_WORKER_OUTPUT', 'surface generator returned an invalid job receipt', 'worker-output', job.generatorTarget));
546
+ }
547
+ else if (receipt.features.some(feature => feature.count < 1 || feature.paths.length < 1)) {
548
+ const missing = receipt.features.filter(feature => feature.count < 1 || feature.paths.length < 1).map(feature => feature.feature).join(', ');
549
+ generatedFailures.set(job.id, diagnostic('SURFACE_FEATURE_COVERAGE', `surface generator did not cover requested features: ${missing}`, 'coverage', job.generatorTarget));
550
+ }
551
+ else if (!samePaths(receipt.reportedPaths, job.expected) || !samePaths(receipt.actualFiles.map(file => file.path), job.expected)) {
552
+ const actual = [...new Set([...receipt.reportedPaths, ...receipt.actualFiles.map(file => file.path)])];
553
+ const missing = job.expected.filter(path => !actual.includes(path));
554
+ const unexpected = actual.filter(path => !job.expected.includes(path));
555
+ generatedFailures.set(job.id, diagnostic('SURFACE_OUTPUT_COVERAGE', `surface generator output did not exactly match the expected path set (missing: ${missing.join(', ') || 'none'}; unexpected: ${unexpected.join(', ') || 'none'})`, 'coverage', job.generatorTarget));
556
+ }
557
+ else
558
+ generatedReceipts.set(job.id, receipt);
559
+ }
560
+ const readerJobs = jobs.filter(job => !generatedFailures.has(job.id));
561
+ if (readerJobs.length === 0)
562
+ return { jobs: generatedFailures };
563
+ const readRequest = JSON.stringify({ version: 1, passRoot: root, identity, limits: WORKER_OUTPUT_LIMITS,
564
+ jobs: readerJobs.map(job => ({ id: job.id, target: job.generatorTarget,
565
+ outputRoot: job.outputRoot, identity: job.outputIdentity,
566
+ expected: job.expected, receiptOutput: job.readerReceipt })),
567
+ });
568
+ const readFailures = (item) => ({
569
+ ...(item.code === ENVIRONMENT_CODES.EXECUTION_TERMINATION_UNCONFIRMED ? { preserveScratch: true } : {}),
570
+ jobs: new Map([
571
+ ...generatedFailures,
572
+ ...readerJobs.map(job => [job.id, { ...item, target: job.generatorTarget }]),
573
+ ]),
574
+ });
575
+ const readStep = step(node, `surface-estate-read-${pass}-${randomBytes(8).toString('hex')}`, root, programs.read, readRequest, deadline);
576
+ if (!readStep)
577
+ return readFailures(diagnostic('SURFACE_WORKER_TIMEOUT', 'surface generator batch deadline expired before output authentication', 'timeout', pass));
578
+ const read = authenticatedExecutionOutcome(await executeProbe(readStep), readStep);
579
+ if (!read || read.kind !== 'complete') {
580
+ return readFailures(read ? executionFailure(read, pass)
581
+ : diagnostic('SURFACE_WORKER_OUTPUT', 'surface output read was not authenticated', 'worker-output', pass));
582
+ }
583
+ if (read.stderr.byteLength > 0) {
584
+ return readFailures(diagnostic('SURFACE_WORKER_STDERR', 'surface output reader emitted a diagnostic', 'stderr', pass, outputText(read.stderr) ?? 'stderr was not valid UTF-8'));
585
+ }
586
+ const readSummaries = receiptSummaries(read.stdout, readerJobs);
587
+ if (!readSummaries)
588
+ return readFailures(diagnostic('SURFACE_WORKER_OUTPUT', 'surface reader returned an invalid estate receipt', 'worker-output', pass));
589
+ const results = new Map(generatedFailures);
590
+ for (const job of readerJobs) {
591
+ const summary = readSummaries.get(job.id);
592
+ if (!summary || 'severity' in summary) {
593
+ results.set(job.id, summary && 'severity' in summary ? summary
594
+ : diagnostic('SURFACE_WORKER_OUTPUT', 'surface reader omitted an estate job receipt', 'worker-output', job.generatorTarget));
595
+ continue;
596
+ }
597
+ const bytes = await readPrivateReceipt(job.readerReceipt, summary);
598
+ const files = bytes ? parseReadReceipt(bytes) : undefined;
599
+ const receipt = generatedReceipts.get(job.id);
600
+ if (!files || !receipt || !samePaths(files.map(file => file.path), job.expected)) {
601
+ results.set(job.id, diagnostic('SURFACE_ARTIFACT_INVALID', 'surface output could not be authenticated as the complete expected set', 'artifact', job.generatorTarget));
602
+ continue;
603
+ }
604
+ const workerByPath = new Map(receipt.actualFiles.map(file => [file.path, file]));
605
+ const readByPath = new Map(files.map(file => [file.path, file]));
606
+ const rendered = [];
607
+ let failure;
608
+ for (const path of job.expected) {
609
+ const worker = workerByPath.get(path);
610
+ const file = readByPath.get(path);
611
+ if (!worker || !file || worker.bytes !== file.bytes || worker.sha256 !== file.sha256) {
612
+ failure = diagnostic('SURFACE_ARTIFACT_INVALID', 'surface output changed between generation and authenticated read', 'artifact', `${job.generatorTarget}:${path}`);
613
+ break;
614
+ }
615
+ let content;
616
+ try {
617
+ content = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.from(file.bytesBase64, 'base64'));
618
+ }
619
+ catch {
620
+ // undecodable bytes are malformed input the caller refuses.
621
+ failure = diagnostic('SURFACE_ARTIFACT_INVALID', 'surface output is not valid UTF-8', 'artifact', `${job.generatorTarget}:${path}`);
622
+ break;
623
+ }
624
+ rendered.push({ target: job.target, path, bytes: content, sha256: file.sha256 });
625
+ }
626
+ results.set(job.id, failure ?? { files: rendered, warnings: receipt.warnings });
627
+ }
628
+ return { jobs: results };
629
+ }
630
+ async function cleanupScratch(scratch, node, program) {
631
+ const cleanup = step(node, `surface-cleanup-${randomBytes(8).toString('hex')}`, dirname(scratch), program, JSON.stringify({ target: scratch, parent: dirname(scratch) }), Date.now() + CLEANUP_TIMEOUT_MS, 1024);
632
+ if (!cleanup)
633
+ return false;
634
+ const outcome = authenticatedExecutionOutcome(await executeProbe(cleanup), cleanup);
635
+ if (!outcome || outcome.kind !== 'complete' || outcome.stderr.byteLength > 0)
636
+ return false;
637
+ try {
638
+ await lstat(scratch);
639
+ return false;
640
+ }
641
+ catch (error) {
642
+ return hasErrorCode(error, 'ENOENT');
643
+ }
644
+ }
645
+ export async function generateSurfaceEstate(input) {
646
+ // Each nested request retains its input limit; their envelope must admit both.
647
+ const captured = snapshotGeneratedJson(input);
648
+ const request = captured !== null && typeof captured === 'object' && !Array.isArray(captured)
649
+ ? captured : undefined;
650
+ const keys = request ? Object.keys(request).sort() : [];
651
+ if (!request || keys.length < 1 || keys.length > 2 || keys.some(key => key !== 'batch' && key !== 'bundle')) {
652
+ return refuse(diagnostic('SURFACE_INPUT_INVALID', 'surface estate generation input is invalid', 'input', 'surface-estate'));
653
+ }
654
+ const batch = request.batch === undefined ? undefined : captureSurfaceBatch(request.batch);
655
+ const bundleRequest = request.bundle === undefined ? undefined : captureSurfaceBundleBatch(request.bundle);
656
+ if (request.batch !== undefined && !batch || request.bundle !== undefined && !bundleRequest || !batch && !bundleRequest) {
657
+ return refuse(diagnostic('SURFACE_INPUT_INVALID', 'surface estate generation input is invalid', 'input', 'surface-estate'));
658
+ }
659
+ const bundleBatch = bundleRequest ? captureSurfaceBatch({
660
+ targets: ['claudecode'],
661
+ features: bundleRequest.features,
662
+ sources: bundleRequest.sources,
663
+ expectedOutputs: { claudecode: bundleRequest.expectedOutputs },
664
+ }) : undefined;
665
+ if (bundleRequest && !bundleBatch) {
666
+ return refuse(diagnostic('SURFACE_INPUT_INVALID', 'surface bundle input could not be bound to the neutral contract', 'input', bundleRequest.format));
667
+ }
668
+ const jobs = [
669
+ ...batch ? batch.targets.map(target => ({
670
+ id: `batch-${target}`,
671
+ kind: 'batch',
672
+ target,
673
+ generatorTarget: harnessFact(target).rulesyncTarget,
674
+ batch,
675
+ features: batch.features.filter(feature => harnessFact(target).rulesyncFeatures.some(candidate => candidate === feature)
676
+ && (feature !== 'mcp' || hasTargetMcpServers(batch.sources, target))
677
+ && (feature !== 'permissions' || (target !== 'codexcli' && target !== 'grokcli')
678
+ || permissionRulesForTarget(batch.sources, target))),
679
+ expected: batch.expectedOutputs[target] ?? EMPTY_FILES,
680
+ })) : [],
681
+ ...bundleRequest && bundleBatch ? [{
682
+ id: `bundle-${bundleRequest.format}`,
683
+ kind: 'bundle',
684
+ target: 'claudecode',
685
+ generatorTarget: bundleRequest.format,
686
+ batch: bundleBatch,
687
+ features: bundleBatch.features,
688
+ expected: bundleRequest.expectedOutputs,
689
+ }] : [],
690
+ ];
691
+ let node;
692
+ try {
693
+ node = await nodeAuthority();
694
+ }
695
+ catch (error) {
696
+ return unavailable(diagnostic('SURFACE_NODE_UNAVAILABLE', `the Node executable could not be authenticated: ${errorCause(error)}`, 'package', process.execPath));
697
+ }
698
+ let bundle;
699
+ try {
700
+ bundle = await bundleAuthority();
701
+ }
702
+ catch (error) {
703
+ return unavailable(diagnostic('SURFACE_PACKAGE_UNAVAILABLE', `the authenticated Rulesync ${RULESYNC_VERSION} generator bundle is unavailable: ${errorCause(error)} — fix: reinstall @b00dea/cockpit`, 'package', 'rulesync-generator.cjs'));
704
+ }
705
+ let programs;
706
+ try {
707
+ programs = surfacePrograms();
708
+ }
709
+ catch (error) {
710
+ return unavailable(diagnostic('SURFACE_PACKAGE_UNAVAILABLE', `the surface generator workers are unavailable: ${errorCause(error)}`, 'package', 'libexec'));
711
+ }
712
+ let scratch;
713
+ try {
714
+ // The cleanup worker compares canonical paths, and the temporary root may be reached
715
+ // through a symlink (macOS /tmp is /private/tmp), so the scratch is canonical from birth.
716
+ scratch = await realpath(await mkdtemp(join(tmpdir(), 'cockpit-surfaces-')));
717
+ await chmod(scratch, 0o700);
718
+ }
719
+ catch (error) {
720
+ return unavailable(diagnostic('SURFACE_SCRATCH_UNAVAILABLE', `private surface staging could not be created: ${errorCause(error)}`, 'package', tmpdir()));
721
+ }
722
+ let outcome;
723
+ let preserveScratch = false;
724
+ const deadline = Date.now() + OPERATION_TIMEOUT_MS;
725
+ try {
726
+ const firstRoot = join(scratch, 'first');
727
+ const secondRoot = join(scratch, 'second');
728
+ await mkdir(firstRoot, { mode: 0o700 });
729
+ await mkdir(secondRoot, { mode: 0o700 });
730
+ const firstJobs = await stageEstatePass(firstRoot, jobs);
731
+ const secondJobs = await stageEstatePass(secondRoot, jobs);
732
+ if (!firstJobs || !secondJobs)
733
+ throw new RefusalError('surface estate roots could not be authenticated');
734
+ const settled = await Promise.allSettled([
735
+ runEstatePass('first', firstRoot, firstJobs, node, bundle, programs, deadline),
736
+ runEstatePass('second', secondRoot, secondJobs, node, bundle, programs, deadline),
737
+ ]);
738
+ const passes = settled.map((result) => result.status === 'fulfilled' ? result.value : {
739
+ preserveScratch: true,
740
+ jobs: new Map(jobs.map(job => [job.id, diagnostic('SURFACE_STAGING_FAILED', errorCause(result.reason), 'artifact', job.generatorTarget)])),
741
+ });
742
+ preserveScratch = passes.some(pass => pass.preserveScratch === true);
743
+ const [first, second] = passes;
744
+ let failure;
745
+ const admitted = new Map();
746
+ for (const job of jobs) {
747
+ const firstResult = first.jobs.get(job.id);
748
+ const secondResult = second.jobs.get(job.id);
749
+ if (!firstResult || !('files' in firstResult)) {
750
+ failure = firstResult ?? diagnostic('SURFACE_WORKER_OUTPUT', 'surface estate omitted a first-pass job result', 'worker-output', job.generatorTarget);
751
+ break;
752
+ }
753
+ if (!secondResult || !('files' in secondResult)) {
754
+ failure = secondResult ?? diagnostic('SURFACE_WORKER_OUTPUT', 'surface estate omitted a second-pass job result', 'worker-output', job.generatorTarget);
755
+ break;
756
+ }
757
+ if (!sameWarnings(firstResult.warnings, secondResult.warnings)
758
+ || !sameRenderedFiles(firstResult.files, secondResult.files)
759
+ || job.kind === 'bundle' && firstResult.warnings.length !== 0) {
760
+ failure = diagnostic('SURFACE_STAGE_VARIANCE', job.kind === 'bundle'
761
+ ? 'independent surface bundle staging produced different authenticated results'
762
+ : 'independent surface staging produced different target, path, byte, hash, or warning sets', 'artifact', job.generatorTarget);
763
+ break;
764
+ }
765
+ admitted.set(job.id, firstResult);
766
+ }
767
+ if (failure)
768
+ outcome = refuse(failure);
769
+ else {
770
+ const coreFiles = jobs.filter(job => job.kind === 'batch').flatMap(job => admitted.get(job.id).files);
771
+ const warnings = jobs.filter(job => job.kind === 'batch').flatMap(job => admitted.get(job.id).warnings);
772
+ const plugin = jobs.find(job => job.kind === 'bundle');
773
+ const pluginFiles = plugin ? admitted.get(plugin.id).files : undefined;
774
+ if (batch && !bundleSurfaceFiles(coreFiles)) {
775
+ outcome = refuse(diagnostic('SURFACE_ARTIFACT_INVALID', 'surface output failed final bundle admission', 'artifact', 'surface-batch'));
776
+ }
777
+ else if (pluginFiles && !bundleSurfaceFiles(pluginFiles)) {
778
+ outcome = refuse(diagnostic('SURFACE_ARTIFACT_INVALID', 'surface bundle failed final admission', 'artifact', plugin.generatorTarget));
779
+ }
780
+ else
781
+ outcome = {
782
+ kind: 'complete',
783
+ ...(batch ? { batch: complete(coreFiles, warnings) } : {}),
784
+ ...(bundleRequest && pluginFiles ? { bundle: completeBundle(bundleRequest.format, pluginFiles) } : {}),
785
+ };
786
+ }
787
+ }
788
+ catch (error) {
789
+ outcome = refuse(diagnostic('SURFACE_STAGING_FAILED', `surface estate staging failed: ${errorCause(error)}`, 'artifact', 'surface-estate'));
790
+ }
791
+ if (preserveScratch) {
792
+ return refuse(diagnostic(ENVIRONMENT_CODES.EXECUTION_TERMINATION_UNCONFIRMED, `surface worker termination could not be confirmed; staging retained at ${scratch}; stop remaining workers before removing it`, 'cleanup', scratch));
793
+ }
794
+ if (!await cleanupScratch(scratch, node, programs.cleanup)) {
795
+ return refuse(diagnostic('SURFACE_CLEANUP_FAILED', 'surface staging cleanup did not complete within its bound', 'cleanup', scratch));
796
+ }
797
+ return outcome;
798
+ }
799
+ export async function generateSurfaceBatch(input) {
800
+ const batch = captureSurfaceBatch(input);
801
+ if (!batch)
802
+ return refuse(diagnostic('SURFACE_INPUT_INVALID', 'surface generation input is invalid', 'input', 'surface-batch'));
803
+ const outcome = await generateSurfaceEstate({ batch });
804
+ return outcome.kind === 'complete' && outcome.batch
805
+ ? outcome.batch
806
+ : outcome.kind === 'complete'
807
+ ? refuse(diagnostic('SURFACE_WORKER_OUTPUT', 'surface estate omitted the batch result', 'worker-output', 'surface-batch'))
808
+ : outcome;
809
+ }