@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,344 @@
1
+ import { RefusalError, ENVIRONMENT_CODES, isCanonicalTimestamp, snapshotEnvironmentObservation, } from './contract.js';
2
+ import { exactKeys } from '../exact-keys.js';
3
+ import { createHash } from 'node:crypto';
4
+ import { observationDeclarations as claimDeclarations } from './claims.js';
5
+ import { surfaceObservationDeclarations } from './generated-application/shared.js';
6
+ import { observationDeclarations as envSchemaDeclarations } from './env-schema-observation.js';
7
+ import { observationDeclarations as gitDeclarations } from './git-observation.js';
8
+ import { observationDeclarations as hostDeclarations } from './host-observation.js';
9
+ import { observationDeclarations as lifecycleStageDeclarations } from './lifecycle-stage.js';
10
+ import { observationDeclarations as runtimeDeclarations, runtimeHealthObservationDeclarations } from './runtime.js';
11
+ import { observationDeclarations as stateDeclarations } from './state.js';
12
+ import { observationDeclarations as templateDeclarations } from './templates.js';
13
+ import { STRUCTURED_INPUT_ENTRY_LIMIT } from '../schema.js';
14
+ import { authenticateObservationPostureContext, POSTURE_REQUIREMENT_OWNER, } from './posture-observation.js';
15
+ export { resolveObservationPostures, refreshObservationPostures } from './posture-observation.js';
16
+ const compareCodeUnits = (left, right) => left < right ? -1 : left > right ? 1 : 0;
17
+ function boundedArray(value) {
18
+ return Array.isArray(value) && value.length <= STRUCTURED_INPUT_ENTRY_LIMIT ? value : undefined;
19
+ }
20
+ function validStage(value) {
21
+ if ((value.stage !== 'bootstrap' && value.stage !== 'steady') || (value.disposition !== 'required' && value.disposition !== 'not-applicable'))
22
+ return false;
23
+ const reason = value.disposition === 'not-applicable' ? value.reason : undefined;
24
+ if (value.disposition === 'required')
25
+ return exactKeys(value, ['stage', 'disposition']);
26
+ return exactKeys(value, ['stage', 'disposition', 'reason']) && typeof reason === 'string' && reason.length > 0 && !reason.includes('\0')
27
+ && Buffer.byteLength(reason, 'utf8') <= 1_024;
28
+ }
29
+ export const allObservationDeclarations = Object.freeze([
30
+ ...claimDeclarations,
31
+ ...envSchemaDeclarations,
32
+ ...gitDeclarations,
33
+ ...hostDeclarations,
34
+ ...lifecycleStageDeclarations,
35
+ ...runtimeDeclarations,
36
+ ...stateDeclarations,
37
+ ...templateDeclarations,
38
+ ...surfaceObservationDeclarations,
39
+ ...runtimeHealthObservationDeclarations,
40
+ ]);
41
+ export function validateObservationDeclarations(declarations, requiredPostures = []) {
42
+ const declarationInputs = boundedArray(declarations);
43
+ const postureInputs = boundedArray(requiredPostures);
44
+ if (!declarationInputs || !postureInputs || postureInputs.some((posture) => typeof posture !== 'string')) {
45
+ return { kind: 'invalid', reason: 'observation declaration collection is malformed' };
46
+ }
47
+ const byId = new Set();
48
+ const byCode = new Set();
49
+ const owned = [];
50
+ for (let declarationIndex = 0; declarationIndex < declarationInputs.length; declarationIndex += 1) {
51
+ const declaration = declarationInputs[declarationIndex];
52
+ if (declaration === null || typeof declaration !== 'object' || !Array.isArray(declaration.postures) || !Array.isArray(declaration.stages)
53
+ || !declaration.stages.every(validStage))
54
+ return { kind: 'invalid', reason: 'observation declaration is malformed' };
55
+ if (typeof declaration.id !== 'string' || typeof declaration.owner !== 'string'
56
+ || !/^[a-z][a-z0-9-]*$/.test(declaration.id) || !/^[a-z][a-z0-9-]*$/.test(declaration.owner)
57
+ || !Object.values(ENVIRONMENT_CODES).includes(declaration.code)
58
+ || typeof declaration.isIssued !== 'function'
59
+ || (declaration.issuedAt !== undefined && typeof declaration.issuedAt !== 'function')
60
+ || declaration.postures.length === 0
61
+ || declaration.postures.some((posture) => typeof posture !== 'string' || !/^[a-z][a-z0-9-]*$/.test(posture))
62
+ || new Set(declaration.postures).size !== declaration.postures.length
63
+ || declaration.stages.length === 0) {
64
+ return { kind: 'invalid', reason: 'observation declaration is malformed' };
65
+ }
66
+ for (const stage of ['bootstrap', 'steady']) {
67
+ const count = declaration.stages.filter((entry) => entry.stage === stage).length;
68
+ if (count === 0)
69
+ return { kind: 'invalid', reason: `${declaration.id} does not declare lifecycle stage ${stage}` };
70
+ if (count > 1)
71
+ return { kind: 'invalid', reason: `${declaration.id} declares lifecycle stage ${stage} more than once` };
72
+ }
73
+ if (declaration.stages.length !== 2)
74
+ return { kind: 'invalid', reason: 'observation declaration is malformed' };
75
+ if (byId.has(declaration.id))
76
+ return { kind: 'invalid', reason: `duplicate observation declaration: ${declaration.id}` };
77
+ byId.add(declaration.id);
78
+ if (byCode.has(declaration.code))
79
+ return { kind: 'invalid', reason: `duplicate observation code: ${declaration.code}` };
80
+ byCode.add(declaration.code);
81
+ for (let postureIndex = 0; postureIndex < postureInputs.length; postureIndex += 1) {
82
+ const posture = postureInputs[postureIndex];
83
+ if (!declaration.postures.includes(posture)) {
84
+ return { kind: 'invalid', reason: `${declaration.id} does not declare posture ${posture}` };
85
+ }
86
+ }
87
+ owned.push(declaration);
88
+ }
89
+ return { kind: 'valid', declarations: owned.sort((left, right) => compareCodeUnits(left.id, right.id)) };
90
+ }
91
+ const productionDeclarations = validateObservationDeclarations(allObservationDeclarations);
92
+ if (productionDeclarations.kind !== 'valid')
93
+ throw new RefusalError(productionDeclarations.reason);
94
+ if (productionDeclarations.declarations.some(({ issuedAt }) => typeof issuedAt !== 'function')) {
95
+ throw new RefusalError('production observation declaration lacks issuance authority');
96
+ }
97
+ const validatedProductionDeclarations = productionDeclarations.declarations;
98
+ const issuedPreflights = new WeakSet();
99
+ // Rows whose evidence exists only while the stack's services run. Before the first `up`, and while
100
+ // the stack sleeps, doctor and preflight still evaluate every other row; these are reported as not
101
+ // applicable with the reason, because reporting them as missing evidence hid the rows that can fail
102
+ // before bring-up behind an "unavailable" verdict.
103
+ const RUNNING_STACK_ROWS = Object.freeze([
104
+ 'running-checkout', 'runtime-availability', 'mount-posture', 'runtime-mount-materialization', 'runtime-port-family',
105
+ ]);
106
+ // Rows and posture checks that describe a runtime; a cockpit that declares none has nothing for them to measure.
107
+ const RUNTIME_ROWS = Object.freeze(['runtime-measurement', POSTURE_REQUIREMENT_OWNER]);
108
+ for (const id of [...RUNNING_STACK_ROWS, ...RUNTIME_ROWS]) {
109
+ if (id !== POSTURE_REQUIREMENT_OWNER && !validatedProductionDeclarations.some((declaration) => declaration.id === id)) {
110
+ throw new RefusalError(`stopped-stack applicability names an undeclared observation: ${id}`);
111
+ }
112
+ }
113
+ /**
114
+ * Which rows cannot be observed when no stack runs, and why. The key is a declaration id, or the
115
+ * posture-requirement owner for the posture checks.
116
+ */
117
+ export function stoppedStackApplicability(input) {
118
+ const rows = new Map(RUNNING_STACK_ROWS.map((id) => [id, `${id} is observed only while the stack runs; run up, then rerun this command to check it`]));
119
+ if (!input.runtimeDeclared) {
120
+ for (const id of [...RUNNING_STACK_ROWS, ...RUNTIME_ROWS])
121
+ rows.set(id, 'this cockpit declares no runtime, so there is nothing for it to measure');
122
+ }
123
+ return rows;
124
+ }
125
+ function jsonEqual(left, right) {
126
+ return JSON.stringify(left) === JSON.stringify(right);
127
+ }
128
+ function hasObservedPresence(value) {
129
+ if (value === null || value === '')
130
+ return false;
131
+ if (Array.isArray(value))
132
+ return value.length > 0;
133
+ return typeof value !== 'object' || Object.keys(value).length > 0;
134
+ }
135
+ export function observationSatisfiesExpectation(observation) {
136
+ if (observation.observed.state !== 'available')
137
+ return false;
138
+ const measured = observation.observed.value;
139
+ const expectation = observation.expectation;
140
+ if (expectation.kind === 'present')
141
+ return hasObservedPresence(measured);
142
+ if (expectation.kind === 'equal')
143
+ return jsonEqual(measured, expectation.value);
144
+ if (expectation.kind === 'one-of')
145
+ return expectation.values.some((value) => jsonEqual(measured, value));
146
+ return typeof measured === 'number' && Number.isSafeInteger(measured)
147
+ && (expectation.kind === 'minimum' ? measured >= expectation.value : measured <= expectation.value);
148
+ }
149
+ function boundedJson(value) {
150
+ const text = JSON.stringify(value);
151
+ return text.length > 120 ? `${text.slice(0, 117)}...` : text;
152
+ }
153
+ // The diagnostic message is this reason, so a bare "does not satisfy" left a human to dig the
154
+ // measured value (such as a dirty-file count) out of the JSON result before acting on it.
155
+ function mismatchDetail(measured, expectation) {
156
+ if (expectation.kind === 'minimum')
157
+ return `measured ${boundedJson(measured)} (expected at least ${expectation.value})`;
158
+ if (expectation.kind === 'maximum')
159
+ return `measured ${boundedJson(measured)} (expected at most ${expectation.value})`;
160
+ if (expectation.kind !== 'equal')
161
+ return undefined;
162
+ const expected = expectation.value;
163
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
164
+ if (!isRecord(measured) || !isRecord(expected))
165
+ return `measured ${boundedJson(measured)} (expected ${boundedJson(expected)})`;
166
+ const fields = [...new Set([...Object.keys(expected), ...Object.keys(measured)])]
167
+ .filter((key) => !jsonEqual(measured[key] ?? null, expected[key] ?? null));
168
+ if (fields.length === 0)
169
+ return undefined;
170
+ const shown = fields.slice(0, 8).map((key) => `${key} is ${boundedJson(measured[key] ?? null)} (expected ${boundedJson(expected[key] ?? null)})`);
171
+ return fields.length > shown.length ? `${shown.join(', ')}, and ${fields.length - shown.length} more` : shown.join(', ');
172
+ }
173
+ function reasonFor(observation, verdict) {
174
+ if (observation.applicability.state === 'not-applicable')
175
+ return observation.applicability.reason;
176
+ if (observation.observed.state !== 'available')
177
+ return observation.observed.reason;
178
+ if (observation.freshness.state !== 'current')
179
+ return observation.freshness.reason;
180
+ if (verdict === 'pass')
181
+ return 'expectation satisfied';
182
+ const detail = mismatchDetail(observation.observed.value, observation.expectation);
183
+ return detail ? `observed value does not satisfy the expectation: ${detail}` : 'observed value does not satisfy the expectation';
184
+ }
185
+ function evaluate(observation) {
186
+ let verdict;
187
+ if (observation.applicability.state === 'not-applicable')
188
+ verdict = 'not-applicable';
189
+ else if (observation.observed.state === 'unavailable')
190
+ verdict = 'unavailable';
191
+ else if (observation.freshness.state !== 'current')
192
+ verdict = 'fail';
193
+ else if (observation.observed.state === 'failed')
194
+ verdict = 'fail';
195
+ else
196
+ verdict = observationSatisfiesExpectation(observation) ? 'pass' : 'fail';
197
+ return {
198
+ code: observation.code,
199
+ owner: observation.owner,
200
+ stackId: observation.stackId,
201
+ source: observation.source,
202
+ verdict,
203
+ reason: reasonFor(observation, verdict),
204
+ remedy: observation.remedy,
205
+ observedAt: observation.observed.observedAt,
206
+ ...(observation.observed.state === 'available' ? { measured: observation.observed.value } : {}),
207
+ expected: observation.expectation,
208
+ };
209
+ }
210
+ function declaredApplicability(declaration, authority, stage, unobservable) {
211
+ const posture = declaration.postures.find((value) => authority.postures.includes(value));
212
+ if (!posture) {
213
+ return { state: 'not-applicable', posture: authority.postures.join('+'), reason: `${declaration.id} does not apply to the resolved posture` };
214
+ }
215
+ const disposition = declaration.stages.find((entry) => entry.stage === stage);
216
+ if (disposition.disposition !== 'required')
217
+ return { state: 'not-applicable', posture, reason: disposition.reason };
218
+ const reason = unobservable.get(declaration.id);
219
+ return reason === undefined ? { state: 'required', posture } : { state: 'not-applicable', posture, reason };
220
+ }
221
+ function missingObservation(declaration, authority, stage, unobservable) {
222
+ return {
223
+ code: declaration.code,
224
+ owner: declaration.owner,
225
+ stackId: authority.stackId,
226
+ source: declaration.id,
227
+ observed: { state: 'unavailable', reason: `${declaration.id} evidence is missing`, observedAt: authority.evaluatedAt },
228
+ expectation: { kind: 'present' },
229
+ applicability: declaredApplicability(declaration, authority, stage, unobservable),
230
+ freshness: { state: 'missing', reason: `${declaration.id} has no current observation` },
231
+ remedy: `provide a current ${declaration.id} observation`,
232
+ };
233
+ }
234
+ /**
235
+ * `unobservable` comes from {@link stoppedStackApplicability}: rows no producer can observe in the
236
+ * stack's current state, reported as not applicable with its reason instead of as missing.
237
+ */
238
+ function evaluatePreflight(values, context, unobservable = new Map()) {
239
+ const observations = [];
240
+ const observationInputs = boundedArray(values);
241
+ if (!observationInputs)
242
+ throw new RefusalError('preflight observations must be a bounded dense array');
243
+ const { evaluationInstant, authorities, stages, checks: postureChecks } = authenticateObservationPostureContext(context);
244
+ if (authorities.size * validatedProductionDeclarations.length > STRUCTURED_INPUT_ENTRY_LIMIT) {
245
+ throw new RefusalError('preflight aggregate exceeds its bounded declaration capacity');
246
+ }
247
+ const suppliedKeys = new Set();
248
+ for (const value of observationInputs) {
249
+ const snapshot = snapshotEnvironmentObservation(value);
250
+ if (snapshot === undefined)
251
+ throw new RefusalError('environment observation is malformed');
252
+ const declaration = validatedProductionDeclarations.find(({ owner, code }) => owner === snapshot.owner && code === snapshot.code);
253
+ if (!declaration)
254
+ throw new RefusalError('environment observation has no declared producer authority');
255
+ if (!declaration.isIssued(value))
256
+ throw new RefusalError('environment observation is not producer-issued');
257
+ const producerIssuedAt = declaration.issuedAt?.(value);
258
+ if (producerIssuedAt === undefined)
259
+ throw new RefusalError('environment observation lacks producer issuance authority');
260
+ const key = `${snapshot.stackId}\0${snapshot.owner}\0${snapshot.code}`;
261
+ if (suppliedKeys.has(key))
262
+ throw new RefusalError('preflight contains duplicate mechanism evidence');
263
+ suppliedKeys.add(key);
264
+ const authority = authorities.get(snapshot.stackId);
265
+ if (!authority)
266
+ throw new RefusalError('environment observation has no resolved posture authority');
267
+ const applicability = declaredApplicability(declaration, authority, stages.get(authority.stackId), unobservable);
268
+ const observedTime = Date.parse(snapshot.observed.observedAt);
269
+ const freshness = observedTime < Date.parse(authority.evaluatedAt)
270
+ || observedTime > Date.parse(authority.validThrough)
271
+ || observedTime > evaluationInstant
272
+ || observedTime > producerIssuedAt
273
+ ? { state: 'stale', reason: 'observation timestamp does not match the evaluation boundary' }
274
+ : snapshot.freshness.state === 'current' ? { state: 'current' } : snapshot.freshness;
275
+ const expectation = snapshot.code === ENVIRONMENT_CODES.CONFIG_STALE
276
+ ? { kind: 'equal', value: authority.revision }
277
+ : snapshot.expectation;
278
+ const owned = snapshotEnvironmentObservation({ ...snapshot, applicability, freshness, expectation });
279
+ if (!owned)
280
+ throw new RefusalError('environment observation applicability could not be derived');
281
+ observations.push(owned);
282
+ }
283
+ const stackIds = [...authorities.keys()].sort(compareCodeUnits);
284
+ const missing = [];
285
+ for (const stackId of stackIds) {
286
+ const authority = authorities.get(stackId);
287
+ for (const declaration of validatedProductionDeclarations) {
288
+ const key = `${stackId}\0${declaration.owner}\0${declaration.code}`;
289
+ if (!suppliedKeys.has(key))
290
+ missing.push([declaration, authority]);
291
+ }
292
+ }
293
+ for (const [declaration, authority] of missing)
294
+ observations.push(missingObservation(declaration, authority, stages.get(authority.stackId), unobservable));
295
+ const postureReason = unobservable.get(POSTURE_REQUIREMENT_OWNER);
296
+ const checks = [...observations
297
+ .sort((left, right) => compareCodeUnits(`${left.stackId}\0${left.code}\0${left.owner}\0${left.source}`, `${right.stackId}\0${right.code}\0${right.owner}\0${right.source}`))
298
+ .map(evaluate), ...postureReason === undefined ? postureChecks
299
+ : postureChecks.map((check) => ({ ...check, verdict: 'not-applicable', reason: postureReason }))]
300
+ .sort((left, right) => compareCodeUnits(`${left.stackId}\0${left.code}\0${left.owner}\0${left.source}`, `${right.stackId}\0${right.code}\0${right.owner}\0${right.source}`));
301
+ const verdict = stackIds.length === 0 && checks.length === 0 ? 'unavailable'
302
+ : checks.some((check) => check.verdict === 'fail')
303
+ ? 'fail'
304
+ : checks.some((check) => check.verdict === 'unavailable') ? 'unavailable' : 'pass';
305
+ const revisionEntries = [...authorities.values()]
306
+ .map((authority) => `${authority.stackId}\0${authority.revision}`)
307
+ .sort(compareCodeUnits);
308
+ const distinctRevisions = new Set([...authorities.values()].map((authority) => authority.revision));
309
+ const revision = distinctRevisions.size === 1
310
+ ? [...distinctRevisions][0]
311
+ : createHash('sha256').update(revisionEntries.join('\0')).digest('hex');
312
+ const result = { verdict, revision, checks };
313
+ issuedPreflights.add(result);
314
+ return result;
315
+ }
316
+ function evidenceValue(value) {
317
+ return value === undefined ? 'unavailable' : JSON.stringify(value);
318
+ }
319
+ export function projectDoctor(preflight) {
320
+ if (!issuedPreflights.has(preflight))
321
+ throw new RefusalError('preflight evaluation is not authentic');
322
+ return {
323
+ verdict: preflight.verdict,
324
+ checks: preflight.checks.map((check) => ({
325
+ ...check,
326
+ evidence: `source=${check.source} observedAt=${check.observedAt} measured=${evidenceValue(check.measured)} expected=${JSON.stringify(check.expected)}`,
327
+ })),
328
+ };
329
+ }
330
+ export function aggregateObservations(groups, context, unobservable) {
331
+ const groupInputs = boundedArray(groups);
332
+ if (!groupInputs)
333
+ throw new RefusalError('observation groups must be a bounded dense array');
334
+ const values = [];
335
+ for (const group of groupInputs) {
336
+ const observations = boundedArray(group);
337
+ if (!observations || values.length + observations.length > STRUCTURED_INPUT_ENTRY_LIMIT) {
338
+ throw new RefusalError('observation groups must be a bounded dense array within the shared boundary');
339
+ }
340
+ for (const observation of observations)
341
+ values.push(observation);
342
+ }
343
+ return evaluatePreflight(values, context, unobservable);
344
+ }
@@ -0,0 +1,50 @@
1
+ import { type EffectivePolicy, type PolicyWeakening } from '../autonomy.js';
2
+ import { type Diagnostic } from './contract.js';
3
+ import type { ResetCommandInput } from './application/lifecycle-context.js';
4
+ import type { GeneratedEnvironmentCommandInput, GeneratedEnvironmentSyncInput, GeneratedStaleRemovalApproval, GeneratedStaleRemovalApprovalRequest } from './generated-application/shared.js';
5
+ export interface OperatorAdmission {
6
+ readonly interactive: boolean;
7
+ readonly json: boolean;
8
+ readonly ci: string | undefined;
9
+ readonly terminal: boolean;
10
+ readonly apply?: boolean;
11
+ }
12
+ /** The autonomy policy a gated command reads, or the refusal that it could not be read. */
13
+ export type OperatorPolicy = Readonly<{
14
+ current: EffectivePolicy;
15
+ recorded?: EffectivePolicy;
16
+ record: () => Promise<void>;
17
+ }>;
18
+ export interface OperatorCommandOutcome {
19
+ readonly status: number;
20
+ readonly diagnostics: readonly Diagnostic[];
21
+ readonly result: unknown;
22
+ }
23
+ export interface OperatorSession {
24
+ readonly preview: OperatorCommandOutcome;
25
+ decide(decision: 'approve' | 'decline'): Promise<OperatorCommandOutcome>;
26
+ }
27
+ export interface ResetOperatorApplicationAdapters {
28
+ readonly preview: (input: ResetCommandInput) => Promise<OperatorCommandOutcome>;
29
+ readonly begin: (input: ResetCommandInput) => Promise<OperatorSession | OperatorCommandOutcome>;
30
+ readonly confirm: (preview: OperatorCommandOutcome) => Promise<boolean>;
31
+ readonly policy: (configPath: string) => Promise<OperatorPolicy | OperatorCommandOutcome>;
32
+ }
33
+ export type GeneratedEnvironmentSyncHandlerInput = GeneratedEnvironmentSyncInput & Readonly<{
34
+ authorizeStaleRemoval?: (request: GeneratedStaleRemovalApprovalRequest) => Promise<GeneratedStaleRemovalApproval | undefined>;
35
+ }>;
36
+ export interface SyncOperatorApplicationAdapters {
37
+ readonly synchronize: (input: GeneratedEnvironmentSyncHandlerInput) => Promise<OperatorCommandOutcome>;
38
+ readonly confirmStaleRemoval: (request: GeneratedStaleRemovalApprovalRequest) => Promise<string | undefined>;
39
+ readonly policy: (configPath: string) => Promise<OperatorPolicy | OperatorCommandOutcome>;
40
+ readonly confirmPolicyWeakening: (request: Readonly<{
41
+ weakened: readonly PolicyWeakening[];
42
+ digest: string;
43
+ }>) => Promise<string | undefined>;
44
+ }
45
+ /** The production policy adapter: cockpit.toml's policy now, the recorded one, and how to record the current one. */
46
+ export declare function loadOperatorPolicy(configPath: string): Promise<OperatorPolicy | OperatorCommandOutcome>;
47
+ export declare function runResetOperatorApplication(input: ResetCommandInput, admission: OperatorAdmission, adapters: ResetOperatorApplicationAdapters): Promise<OperatorCommandOutcome>;
48
+ export declare function runSyncOperatorApplication(input: GeneratedEnvironmentCommandInput & Readonly<{
49
+ dryRun?: boolean;
50
+ }>, admission: OperatorAdmission, adapters: SyncOperatorApplicationAdapters): Promise<OperatorCommandOutcome>;
@@ -0,0 +1,155 @@
1
+ import { decide, policyWeakening, policyWeakeningDiagnostic, weakeningDigest, writeRecordedPolicy } from '../autonomy.js';
2
+ import { loadAutonomyPolicy } from '../config/load.js';
3
+ import { ENVIRONMENT_CODES } from './contract.js';
4
+ import { EXIT_CODES } from '../invocation.js';
5
+ import { refusalOutcome as refusal } from '../refusal.js';
6
+ const isPolicy = (value) => 'current' in value;
7
+ /** The production policy adapter: cockpit.toml's policy now, the recorded one, and how to record the current one. */
8
+ export async function loadOperatorPolicy(configPath) {
9
+ const loaded = await loadAutonomyPolicy(configPath);
10
+ if ('refusal' in loaded)
11
+ return { status: 2, result: null, diagnostics: [loaded.refusal] };
12
+ return { current: loaded.current, ...loaded.recorded ? { recorded: loaded.recorded } : {}, record: () => writeRecordedPolicy(loaded.root, loaded.current) };
13
+ }
14
+ /** Why `decider` does not let this run pass the decision, as the refusal a command returns, or undefined when it does. */
15
+ function autonomyRefusal(field, decider, admission) {
16
+ const decision = decide(decider, { CI: admission.ci });
17
+ if (decision === 'proceed')
18
+ return undefined;
19
+ // Each code is a literal so the diagnostic inventory reads its fix beside it.
20
+ return decision === 'person'
21
+ ? { status: 2, result: null, diagnostics: [{ severity: 'error', target: field,
22
+ code: 'AUTONOMY_PERSON_REQUIRED',
23
+ message: `[autonomy] ${field} = "person" in cockpit.toml: this run cannot take the decision without a person`,
24
+ fix: 'a person runs the same command with --interactive in a terminal outside CI, or a person changes the field in cockpit.toml and accepts the change at sync' }] }
25
+ : { status: 2, result: null, diagnostics: [{ severity: 'error', target: field,
26
+ code: 'AUTONOMY_CI_ONLY',
27
+ message: `[autonomy] ${field} = "ci" in cockpit.toml: this decision is taken only in CI, behind the reviewers of its protected environment`,
28
+ fix: 'push the change and let the CI job run the command, or have a person change the field in cockpit.toml and accept the change at sync' }] };
29
+ }
30
+ // A weakening the last sync did not accept is not in force: the command it would let through
31
+ // refuses, as sync does, until a person accepts it there or the change is reverted.
32
+ function weakeningRefusal(weakened, unchanged, result = null) {
33
+ const diagnostic = policyWeakeningDiagnostic(weakened);
34
+ return { status: 2, result, diagnostics: [{ ...diagnostic, message: `${diagnostic.message}; ${unchanged}` }] };
35
+ }
36
+ export async function runResetOperatorApplication(input, admission, adapters) {
37
+ if (!admission.interactive && admission.apply) {
38
+ const policy = await adapters.policy(input.configPath);
39
+ if (!isPolicy(policy))
40
+ return policy;
41
+ const weakened = policy.recorded ? policyWeakening(policy.recorded, policy.current).filter(row => row.field === 'autonomy.reset') : [];
42
+ if (weakened.length)
43
+ return weakeningRefusal(weakened, 'nothing was reset');
44
+ const refused = autonomyRefusal('reset', policy.current.autonomy.reset, admission);
45
+ if (refused)
46
+ return refused;
47
+ const opened = await adapters.begin(input);
48
+ return 'decide' in opened ? opened.decide('approve') : opened;
49
+ }
50
+ if (!admission.interactive)
51
+ return adapters.preview(input);
52
+ if (admission.json || admission.ci && admission.ci.length > 0) {
53
+ return refusal(ENVIRONMENT_CODES.RESET_CONFIRMATION_MISMATCH, 'interactive reset requires a human terminal outside CI and JSON modes', 'reset');
54
+ }
55
+ if (!admission.terminal) {
56
+ return refusal(ENVIRONMENT_CODES.RESET_CONFIRMATION_MISMATCH, 'interactive reset requires terminal input', 'reset');
57
+ }
58
+ const opened = await adapters.begin(input);
59
+ if (!('decide' in opened))
60
+ return opened;
61
+ const approved = await adapters.confirm(opened.preview);
62
+ if (approved)
63
+ return opened.decide('approve');
64
+ await opened.decide('decline');
65
+ return { status: EXIT_CODES.cancelled, diagnostics: [], result: null };
66
+ }
67
+ function staleRemovalRefusal(message) {
68
+ return refusal('GENERATED_STALE_APPROVAL_REQUIRED', message, 'generated-environment');
69
+ }
70
+ function previewedStaleRemoval(outcome) {
71
+ if (outcome.status !== 0 || !outcome.result || typeof outcome.result !== 'object')
72
+ return { state: 'invalid' };
73
+ const result = outcome.result;
74
+ if (result.dryRun !== true)
75
+ return { state: 'invalid' };
76
+ if (!Object.hasOwn(result, 'staleRemoval'))
77
+ return { state: 'none' };
78
+ const candidate = result.staleRemoval;
79
+ if (!candidate || typeof candidate !== 'object')
80
+ return { state: 'invalid' };
81
+ const request = candidate;
82
+ if (request.format !== 'cockpit-stale-removal-request/v1'
83
+ || typeof request.cockpitId !== 'string' || request.cockpitId.length === 0
84
+ || typeof request.generation !== 'string' || request.generation.length === 0
85
+ || typeof request.sourceSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(request.sourceSha256)
86
+ || typeof request.digest !== 'string' || !/^[a-f0-9]{64}$/.test(request.digest)
87
+ || !Array.isArray(request.entries) || request.entries.length === 0)
88
+ return { state: 'invalid' };
89
+ return { state: 'stale', request: candidate };
90
+ }
91
+ // The one place sync's decision lives: who may write, whether a weakened policy was accepted, and
92
+ // whether stale generated files go without a person. A dry run writes nothing and passes untouched.
93
+ export async function runSyncOperatorApplication(input, admission, adapters) {
94
+ if (input.dryRun === true)
95
+ return adapters.synchronize(input);
96
+ if (admission.interactive && (admission.json || admission.ci && admission.ci.length > 0)) {
97
+ return staleRemovalRefusal('interactive sync requires a human terminal outside CI and JSON modes');
98
+ }
99
+ if (admission.interactive && !admission.terminal)
100
+ return staleRemovalRefusal('interactive sync requires terminal input');
101
+ const policy = await adapters.policy(input.configPath);
102
+ if (!isPolicy(policy))
103
+ return policy;
104
+ // A person's sync is the person's decision; only a sync gated on ci is refused outside CI here, and
105
+ // the guard keeps an agent's sync from running unless [autonomy] sync is agent.
106
+ if (policy.current.autonomy.sync === 'ci') {
107
+ const refused = autonomyRefusal('sync', 'ci', admission);
108
+ if (refused)
109
+ return refused;
110
+ }
111
+ const weakened = policy.recorded ? policyWeakening(policy.recorded, policy.current) : [];
112
+ if (weakened.length) {
113
+ const digest = weakeningDigest(policy.recorded, policy.current);
114
+ if (!admission.interactive)
115
+ return weakeningRefusal(weakened, 'nothing was written', { weakened, digest });
116
+ if (await adapters.confirmPolicyWeakening({ weakened, digest }) !== digest) {
117
+ return refusal('POLICY_WEAKENING_UNCONFIRMED', 'the typed digest did not match the policy weakening shown; nothing was written', 'autonomy');
118
+ }
119
+ }
120
+ // Stale removal an agent may take is approved for exactly the entries this sync computes.
121
+ const staleByPolicy = decide(policy.current.autonomy.stale_delete, { CI: admission.ci }) === 'proceed';
122
+ const approveAll = async (request) => ({ format: 'cockpit-stale-removal-approval/v1', digest: request.digest });
123
+ let outcome;
124
+ if (!admission.interactive || staleByPolicy) {
125
+ outcome = await adapters.synchronize({ ...input, ...staleByPolicy ? { authorizeStaleRemoval: approveAll } : {} });
126
+ }
127
+ else {
128
+ const preview = await adapters.synchronize({ ...input, dryRun: true });
129
+ if (preview.status !== 0)
130
+ return preview;
131
+ const inspected = previewedStaleRemoval(preview);
132
+ if (inspected.state === 'invalid') {
133
+ return refusal('CLI_INTERNAL_ERROR', 'interactive sync received an invalid dry-run result', 'sync');
134
+ }
135
+ if (inspected.state === 'none')
136
+ outcome = await adapters.synchronize(input);
137
+ else {
138
+ const typedDigest = await adapters.confirmStaleRemoval(inspected.request);
139
+ if (typedDigest !== inspected.request.digest) {
140
+ return staleRemovalRefusal('the typed stale-removal digest did not match the exact preview');
141
+ }
142
+ const previewBytes = JSON.stringify(inspected.request);
143
+ outcome = await adapters.synchronize({
144
+ ...input,
145
+ authorizeStaleRemoval: async (request) => request.digest === inspected.request.digest
146
+ && JSON.stringify(request) === previewBytes
147
+ ? { format: 'cockpit-stale-removal-approval/v1', digest: request.digest }
148
+ : undefined,
149
+ });
150
+ }
151
+ }
152
+ if (outcome.status === 0)
153
+ await policy.record();
154
+ return outcome;
155
+ }