@b00dea/cockpit 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -1
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +76 -1
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,310 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { initCommand } from '../fix-catalogue.js';
3
+ import { runInitApplication, unsupportedInitPlatformOutcome, } from '../environment/init-application.js';
4
+ import { refusalOutcome as refusal } from '../refusal.js';
5
+ // Real interviews ask up to eleven questions; the bound only stops an application that never converges from looping forever.
6
+ const MAX_INTERVIEW_ANSWERS = 64;
7
+ const ANSWER_FIELDS = ['cockpit.root', 'cockpit.name', 'sibling', 'estate.runnable', 'ports.bases',
8
+ 'environment.install_policies', 'environment.test_data', 'environment.compose_files',
9
+ 'environment.machine', 'environment.posture', 'testing', 'guards'];
10
+ /**
11
+ * The answers file's optional [provenance] table says who chose an answer: "human" (the default) or
12
+ * "agent-default" when the setup agent picked it because the person let it choose. Without it, every
13
+ * value an agent invented was written into cockpit.toml as something the operator stated.
14
+ */
15
+ const PROVENANCE_TABLE = 'provenance';
16
+ const PROVENANCE_VALUES = { human: 'operator-confirmed', 'agent-default': 'agent-default' };
17
+ // cockpit never proposes these, and the docs say they come from a person; an answers file that does
18
+ // not attribute one is recorded as unattributed rather than as the person's.
19
+ const UNATTRIBUTED_UNLESS_MARKED = ['environment.machine'];
20
+ const isTable = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);
21
+ /** Flattens the answers file to field -> value, naming any key that matches no interview field. */
22
+ function answersByField(answers) {
23
+ const fields = new Map();
24
+ const unknown = [];
25
+ const walk = (table, prefix) => {
26
+ for (const [key, value] of Object.entries(table)) {
27
+ const path = prefix === '' ? key : `${prefix}.${key}`;
28
+ if (path === PROVENANCE_TABLE)
29
+ continue;
30
+ if (ANSWER_FIELDS.includes(path))
31
+ fields.set(path, value);
32
+ else if (isTable(value) && ANSWER_FIELDS.some(field => field.startsWith(`${path}.`)))
33
+ walk(value, path);
34
+ else
35
+ unknown.push(path);
36
+ }
37
+ };
38
+ walk(answers, '');
39
+ return { fields, unknown };
40
+ }
41
+ /** Reads the [provenance] table into field -> provenance, or names what is wrong with it. */
42
+ function answerProvenance(answers, answered) {
43
+ const marks = new Map();
44
+ const table = answers[PROVENANCE_TABLE];
45
+ if (table === undefined)
46
+ return { marks };
47
+ if (!isTable(table))
48
+ return { refusal: refusal('INIT_ANSWER_INVALID', `${PROVENANCE_TABLE} must be a table of answer field = "human" or "agent-default"`) };
49
+ const problems = [];
50
+ const walk = (current, prefix) => {
51
+ for (const [key, value] of Object.entries(current)) {
52
+ const path = prefix === '' ? key : `${prefix}.${key}`;
53
+ if (isTable(value)) {
54
+ walk(value, path);
55
+ continue;
56
+ }
57
+ if (!ANSWER_FIELDS.includes(path) || path === 'cockpit.root') {
58
+ problems.push(`provenance names no recorded interview answer: ${path}`);
59
+ }
60
+ else if (typeof value !== 'string' || PROVENANCE_VALUES[value] === undefined) {
61
+ problems.push(`provenance.${path} must be "human" or "agent-default"`);
62
+ }
63
+ else if (!answered.has(path)) {
64
+ problems.push(`provenance names ${path}, which the answers file does not answer`);
65
+ }
66
+ else
67
+ marks.set(path, PROVENANCE_VALUES[value]);
68
+ }
69
+ };
70
+ walk(table, '');
71
+ return problems.length === 0 ? { marks }
72
+ : { refusal: refusal('INIT_ANSWER_INVALID', `${problems.join('; ')}; the [provenance] table says who chose each answer and is optional`) };
73
+ }
74
+ /** Who chose the answer to one question, as the answers file records it. */
75
+ function provenanceFor(field, marks) {
76
+ if (field === 'configuration.defaults')
77
+ return 'operator-confirmed';
78
+ return marks.get(field) ?? (UNATTRIBUTED_UNLESS_MARKED.includes(field) ? 'unattributed' : 'operator-confirmed');
79
+ }
80
+ /** Records every field the interview has asked or will ask, so a supplied field it never asks is reported. */
81
+ function noteInterview(asked, outcome) {
82
+ const interview = outcome.result?.interview;
83
+ if (!interview)
84
+ return;
85
+ if (interview.cockpitRoot !== undefined)
86
+ asked.add('cockpit.root');
87
+ for (const field of [...interview.answered, ...interview.outstanding.map(row => row.field)])
88
+ asked.add(field);
89
+ }
90
+ function unattendedAnswer(field, expectedSha256, supplied, unattended) {
91
+ if (field === 'configuration.defaults') {
92
+ if (unattended.defaultsSha256 === undefined)
93
+ return { kind: 'missing' };
94
+ if (unattended.defaultsSha256 !== expectedSha256)
95
+ return { kind: 'refusal', outcome: refusal('INIT_DEFAULTS_NOT_ACCEPTED', '--accept-defaults does not equal the digest of the surfaced defaults; rerun without it to read the current digest') };
96
+ return { kind: 'answer', bytes: JSON.stringify({ confirmed: true, sha256: unattended.defaultsSha256 }) };
97
+ }
98
+ const value = supplied.get(field);
99
+ if (value === undefined)
100
+ return { kind: 'missing' };
101
+ try {
102
+ return { kind: 'answer', bytes: JSON.stringify(value) };
103
+ }
104
+ catch (error) {
105
+ return { kind: 'refusal', outcome: refusal('INIT_ANSWER_INVALID', `the answer for ${field} cannot be written as JSON (${error instanceof Error ? error.message : 'unknown error'})`) };
106
+ }
107
+ }
108
+ const canonicalJson = (value) => Array.isArray(value) ? `[${value.map(canonicalJson).join(',')}]`
109
+ : isTable(value) ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`
110
+ : JSON.stringify(value);
111
+ /**
112
+ * An answer an earlier run recorded stays in force, so a different value in the file must not be
113
+ * accepted quietly: the person approved the file's bytes, not the recorded ones.
114
+ */
115
+ function recordedAnswerConflict(input, preview, supplied, marks, unattended) {
116
+ const placement = preview.result?.interview?.cockpitRoot;
117
+ const suppliedPlacement = supplied.get('cockpit.root');
118
+ if (placement !== undefined && suppliedPlacement !== undefined && suppliedPlacement !== placement)
119
+ return refusal('INIT_ANSWER_CONFLICT', `the answers file gives cockpit.root = ${JSON.stringify(suppliedPlacement)}, but this cockpit was created ${placement === 'in-repo' ? 'inside the product repository' : 'in a dedicated folder'}; init does not move an existing cockpit, so drop cockpit.root from the answers file or set it to ${JSON.stringify(placement)}`);
120
+ const answered = preview.result?.interview?.answered ?? [];
121
+ const recorded = preview.result?.estate?.answers ?? [];
122
+ for (const [index, field] of answered.entries()) {
123
+ // The defaults are accepted by digest, not by the answers file, and are bound to the draft they confirmed.
124
+ if (field === 'configuration.defaults')
125
+ continue;
126
+ const now = unattendedAnswer(field, undefined, supplied, unattended);
127
+ if (now.kind === 'refusal')
128
+ return now.outcome;
129
+ if (now.kind !== 'answer' || recorded[index] === undefined)
130
+ continue;
131
+ let same = false;
132
+ // A recorded or supplied answer that is not JSON cannot match, which the conflict refusal below reports.
133
+ try {
134
+ same = canonicalJson(JSON.parse(now.bytes)) === canonicalJson(JSON.parse(recorded[index].bytes));
135
+ }
136
+ catch {
137
+ same = false;
138
+ }
139
+ if (same && provenanceFor(field, marks) !== recorded[index].provenance)
140
+ same = false;
141
+ if (!same)
142
+ return refusal('INIT_ANSWER_CONFLICT', `the answers file gives a different value for ${field} than the answer an earlier run recorded; drop ${field} from the answers file to keep the recorded answer, or, before cockpit.toml is prepared, preview a reset with ${initCommand({ root: input.root, target: (input.tools ?? [input.target]).join(','), args: `--reset-answer ${field} --json` })}`);
143
+ }
144
+ return undefined;
145
+ }
146
+ export async function runInitOperatorCommand(input, options, io, application) {
147
+ const platform = input.platform ?? process.platform;
148
+ if (platform !== 'darwin' && platform !== 'linux')
149
+ return unsupportedInitPlatformOutcome(platform);
150
+ const asked = new Set();
151
+ const outcome = await drive({ ...input, platform }, options, io, application, asked);
152
+ if (!options.unattended || outcome.kind !== 'complete' || asked.size === 0)
153
+ return outcome;
154
+ // A finished run that ignored part of the file would otherwise look like it honoured all of it.
155
+ const ignored = [...answersByField(options.unattended.answers).fields.keys()]
156
+ .filter(field => !asked.has(field));
157
+ if (ignored.length === 0)
158
+ return outcome;
159
+ return { ...outcome, diagnostics: [...outcome.diagnostics, { severity: 'warning', code: 'INIT_ANSWER_UNUSED',
160
+ message: `the answers file gave ${ignored.join(', ')}, which the interview never asked for` }] };
161
+ }
162
+ async function drive(input, options, io, application, asked) {
163
+ let shared = {
164
+ root: input.root,
165
+ target: input.target,
166
+ tools: input.tools ?? [input.target],
167
+ ...(input.registry === undefined ? {} : { registry: input.registry }),
168
+ sessionId: input.sessionId ?? randomUUID(),
169
+ platform: input.platform,
170
+ home: input.home ?? process.env.HOME,
171
+ ...(input.xdgStateHome === undefined ? {} : { xdgStateHome: input.xdgStateHome }),
172
+ ...(input.resetAnswer === undefined ? {} : { resetAnswer: input.resetAnswer }),
173
+ };
174
+ if (input.resetAnswer !== undefined) {
175
+ const resetPreview = { field: input.resetAnswer.field };
176
+ const direct = await application({ ...shared, resetAnswer: resetPreview, accepted: false });
177
+ if (direct.kind === 'pending' && direct.result?.resetAnswer?.field === input.resetAnswer.field) {
178
+ if (input.resetAnswer.acceptedPreviewSha256 === undefined)
179
+ return direct;
180
+ return application({ ...shared, accepted: false });
181
+ }
182
+ if (direct.kind !== 'refusal' || direct.diagnostics.length !== 1
183
+ || direct.diagnostics[0]?.code !== 'INIT_ANSWER_RESET_UNAVAILABLE')
184
+ return direct;
185
+ const { resetAnswer: _resetAnswer, ...discoveryInput } = shared;
186
+ const discovery = await application({ ...discoveryInput, accepted: false });
187
+ const option = discovery.result?.cockpitRoot?.options.find(row => row.answer === 'dedicated');
188
+ if (!option || option.unavailable !== undefined)
189
+ return direct;
190
+ const routed = { ...shared, root: option.root,
191
+ cockpitRoot: { kind: 'dedicated', firstSibling: option.firstSibling } };
192
+ const preview = await application({ ...routed, resetAnswer: resetPreview, accepted: false });
193
+ if (input.resetAnswer.acceptedPreviewSha256 === undefined || preview.kind !== 'pending'
194
+ || preview.result?.resetAnswer?.field !== input.resetAnswer.field)
195
+ return preview;
196
+ return application({ ...routed, accepted: false });
197
+ }
198
+ const unattended = options.unattended;
199
+ const supplied = unattended ? answersByField(unattended.answers) : undefined;
200
+ if (supplied && supplied.unknown.length > 0)
201
+ return refusal('INIT_ANSWER_FIELD_UNKNOWN', `the answers file names no interview field: ${supplied.unknown.join(', ')}; valid fields are ${ANSWER_FIELDS.join(', ')}, plus the optional [${PROVENANCE_TABLE}] table`);
202
+ const provenance = unattended && supplied ? answerProvenance(unattended.answers, supplied.fields) : { marks: new Map() };
203
+ if ('refusal' in provenance)
204
+ return provenance.refusal;
205
+ const { marks } = provenance;
206
+ let preview = await application({ ...shared, accepted: false });
207
+ noteInterview(asked, preview);
208
+ if (preview.kind !== 'pending' || preview.status !== 0)
209
+ return preview;
210
+ if (!unattended && (!options.interactive || options.json))
211
+ return preview;
212
+ if (!unattended && !io.terminal)
213
+ return refusal('INIT_TERMINAL_REQUIRED', 'interactive init requires an attached terminal; an agent without one passes --accept-preview and --answers instead');
214
+ const rootQuestion = preview.result?.cockpitRoot;
215
+ if (rootQuestion) {
216
+ asked.add(rootQuestion.field);
217
+ const answer = unattended ? supplied.fields.get(rootQuestion.field) : await io.chooseCockpitRoot?.(rootQuestion);
218
+ // No answer leaves the choice to the application, which refuses acceptance and names the key.
219
+ const option = rootQuestion.options.find(row => row.answer === answer);
220
+ if (answer !== undefined && !option)
221
+ return refusal('INIT_ANSWER_INVALID', `cockpit.root must be one of ${rootQuestion.options.map(row => JSON.stringify(row.answer)).join(' or ')}`);
222
+ // The preview digest does not depend on the root, so a stale one is refused before the folder is created.
223
+ if (option && unattended && unattended.previewSha256 !== preview.result.previewSha256) {
224
+ return refusal('INIT_PREVIEW_NOT_ACCEPTED', '--accept-preview does not equal the digest of the pending preview; rerun without it to read the current digest');
225
+ }
226
+ if (option?.unavailable !== undefined)
227
+ return refusal('INIT_COCKPIT_ROOT_UNAVAILABLE', `cockpit.root = ${JSON.stringify(option.answer)} cannot be used here: ${option.unavailable}`);
228
+ if (option?.answer === 'dedicated') {
229
+ try {
230
+ if (!io.createCockpitRoot)
231
+ throw new Error('this caller cannot create directories');
232
+ await io.createCockpitRoot(option.root);
233
+ }
234
+ catch (error) {
235
+ return refusal('INIT_COCKPIT_ROOT_CREATE_FAILED', `the dedicated cockpit folder ${option.root} could not be created (${error instanceof Error ? error.message : 'unknown error'}); create it yourself or answer cockpit.root = "in-repo"`);
236
+ }
237
+ shared = { ...shared, root: option.root, cockpitRoot: { kind: 'dedicated', firstSibling: option.firstSibling } };
238
+ }
239
+ else if (option)
240
+ shared = { ...shared, cockpitRoot: { kind: 'in-repo' } };
241
+ if (option) {
242
+ preview = await application({ ...shared, accepted: false });
243
+ noteInterview(asked, preview);
244
+ if (preview.kind !== 'pending' || preview.status !== 0)
245
+ return preview;
246
+ }
247
+ }
248
+ const accepted = { ...shared, accepted: true, acceptedPreviewSha256: preview.result.previewSha256 };
249
+ let outcome;
250
+ if (unattended && unattended.previewSha256 !== preview.result.previewSha256) {
251
+ return refusal('INIT_PREVIEW_NOT_ACCEPTED', '--accept-preview does not equal the digest of the pending preview; rerun without it to read the current digest');
252
+ }
253
+ if (unattended && supplied) {
254
+ const conflict = recordedAnswerConflict(input, preview, supplied.fields, marks, unattended);
255
+ if (conflict)
256
+ return conflict;
257
+ }
258
+ if (preview.result?.question === undefined && !unattended) {
259
+ const answer = await io.confirm(preview.result);
260
+ if (answer !== preview.result.previewSha256) {
261
+ return refusal('INIT_PREVIEW_NOT_ACCEPTED', 'type the exact preview digest to accept the selected bootstrap target');
262
+ }
263
+ outcome = await application(accepted);
264
+ }
265
+ else if (preview.result?.question === undefined)
266
+ outcome = await application(accepted);
267
+ else
268
+ outcome = preview;
269
+ for (let answered = 0; outcome.kind === 'pending' && outcome.result?.question !== undefined; answered += 1) {
270
+ if (answered >= MAX_INTERVIEW_ANSWERS)
271
+ return refusal('INIT_INTERVIEW_LIMIT_EXCEEDED', `the interview still asks questions after ${MAX_INTERVIEW_ANSWERS} answers; rerun ${initCommand({ root: input.root, target: (input.tools ?? [input.target]).join(',') })} with the same options to resume from the persisted answers`);
272
+ const question = outcome.result.question;
273
+ let bytes;
274
+ // A person typing at the terminal states their own answer.
275
+ let answerProvenanceValue = 'operator-confirmed';
276
+ if (unattended && supplied) {
277
+ const resolved = unattendedAnswer(question.field, question.expectedSha256, supplied.fields, unattended);
278
+ if (resolved.kind === 'refusal')
279
+ return resolved.outcome;
280
+ // An unanswered field is not a failure: the pending result lists what is still outstanding.
281
+ if (resolved.kind === 'missing')
282
+ return outcome;
283
+ bytes = resolved.bytes;
284
+ answerProvenanceValue = provenanceFor(question.field, marks);
285
+ }
286
+ else {
287
+ if (!io.answerQuestion)
288
+ return outcome;
289
+ bytes = await io.answerQuestion(question);
290
+ if (bytes === undefined)
291
+ return refusal('GREENFIELD_ANSWER_REQUIRED', 'greenfield init requires an explicit answer to the named question');
292
+ }
293
+ outcome = await application({ ...accepted, greenfieldAnswer: {
294
+ questionId: question.id, bytes, provenance: answerProvenanceValue,
295
+ } });
296
+ noteInterview(asked, outcome);
297
+ }
298
+ if (outcome.kind !== 'artifact' || outcome.artifact === null || outcome.destination === null)
299
+ return outcome;
300
+ try {
301
+ await io.writeArtifact(outcome.destination, outcome.artifact);
302
+ }
303
+ catch (error) {
304
+ if (error.code !== 'EEXIST') {
305
+ return refusal('INIT_ARTIFACT_WRITE_FAILED', `cockpit.toml could not be written (${error.code ?? 'unknown I/O error'})`);
306
+ }
307
+ }
308
+ outcome = await application(accepted);
309
+ return outcome;
310
+ }
@@ -0,0 +1,51 @@
1
+ import { type Diagnostic } from '../environment/contract.js';
2
+ export interface InferredProvenance {
3
+ readonly kind: 'inferred';
4
+ readonly source: string;
5
+ readonly reason: string;
6
+ }
7
+ export interface InferredFact {
8
+ /** Interview fields this fact helps answer. */
9
+ readonly fields: readonly string[];
10
+ readonly target: string;
11
+ readonly value: unknown;
12
+ readonly provenance: InferredProvenance;
13
+ }
14
+ export interface SkippedInference {
15
+ readonly source: string;
16
+ readonly reason: string;
17
+ }
18
+ export interface InferenceResult {
19
+ readonly facts: readonly InferredFact[];
20
+ readonly skipped: readonly SkippedInference[];
21
+ }
22
+ export interface InferenceInput {
23
+ /** Files must stay under this observed authority root. */
24
+ readonly root: string;
25
+ /** Paths surfaced for confirmation are relative to the cockpit that will store them. */
26
+ readonly configurationRoot?: string;
27
+ readonly repositories: readonly Readonly<{
28
+ slug: string;
29
+ path: string;
30
+ }>[];
31
+ }
32
+ export declare const COMPOSE_NAMES: readonly ['compose.yaml', 'compose.yml', 'docker-compose.yaml', 'docker-compose.yml'];
33
+ export interface DockerfileFacts {
34
+ readonly command?: string[];
35
+ readonly ports: number[];
36
+ }
37
+ export declare function readDockerfileFacts(text: string): DockerfileFacts;
38
+ /** The lockfiles a toolchain is proposed from, all of them Node's: the only ones this module maps to a schema toolchain. */
39
+ export declare const NODE_LOCKFILE_NAMES: readonly string[];
40
+ export declare function deriveInferredFacts(input: InferenceInput): Promise<InferenceResult>;
41
+ /**
42
+ * What `check` would refuse or warn about in the Compose files a drafted cockpit.toml declares,
43
+ * judged by check's own rules over the files as written: a host port outside `ports.bases`, a service
44
+ * with no healthcheck. Init never edits a Compose file, so these are shown beside the draft, with
45
+ * check's fix, to be applied to the file before or right after the draft is accepted. Findings are
46
+ * warnings here because nothing is refused until check runs.
47
+ */
48
+ export declare function composeFindings(input: Readonly<{
49
+ root: string;
50
+ draft: string;
51
+ }>): Promise<readonly Diagnostic[]>;