@b00dea/cockpit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE +21 -0
  3. package/README.md +25 -2
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2644 -0
  6. package/dist/autonomy.d.ts +45 -0
  7. package/dist/autonomy.js +173 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +117 -0
  11. package/dist/browser/commands.js +453 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +118 -0
  17. package/dist/browser/runtime.js +186 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +561 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/canonical-json.d.ts +2 -0
  23. package/dist/canonical-json.js +5 -0
  24. package/dist/cli-guard.d.ts +5 -0
  25. package/dist/cli-guard.js +71 -0
  26. package/dist/cli-main.d.ts +176 -0
  27. package/dist/cli-main.js +1584 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.js +16 -0
  30. package/dist/command-table.d.ts +121 -0
  31. package/dist/command-table.js +664 -0
  32. package/dist/config/load.d.ts +183 -0
  33. package/dist/config/load.js +367 -0
  34. package/dist/config/sibling-toml.d.ts +48 -0
  35. package/dist/config/sibling-toml.js +160 -0
  36. package/dist/config/surface-tools-toml.d.ts +9 -0
  37. package/dist/config/surface-tools-toml.js +37 -0
  38. package/dist/config/unconfirmed-toml.d.ts +9 -0
  39. package/dist/config/unconfirmed-toml.js +29 -0
  40. package/dist/config/verified-write.d.ts +43 -0
  41. package/dist/config/verified-write.js +89 -0
  42. package/dist/diagnostics.d.ts +26 -0
  43. package/dist/diagnostics.js +344 -0
  44. package/dist/docs/check.d.ts +16 -0
  45. package/dist/docs/check.js +197 -0
  46. package/dist/docs/decision-record.d.ts +34 -0
  47. package/dist/docs/decision-record.js +135 -0
  48. package/dist/docs/decisions.d.ts +16 -0
  49. package/dist/docs/decisions.js +103 -0
  50. package/dist/docs/markdown.d.ts +36 -0
  51. package/dist/docs/markdown.js +88 -0
  52. package/dist/docs/references.d.ts +32 -0
  53. package/dist/docs/references.js +148 -0
  54. package/dist/emit.d.ts +22 -0
  55. package/dist/emit.js +43 -0
  56. package/dist/entrypoint-kinds.d.ts +3 -0
  57. package/dist/entrypoint-kinds.js +11 -0
  58. package/dist/environment/application/confirm.d.ts +35 -0
  59. package/dist/environment/application/confirm.js +122 -0
  60. package/dist/environment/application/destructive.d.ts +6 -0
  61. package/dist/environment/application/destructive.js +429 -0
  62. package/dist/environment/application/doctor.d.ts +94 -0
  63. package/dist/environment/application/doctor.js +247 -0
  64. package/dist/environment/application/guard.d.ts +49 -0
  65. package/dist/environment/application/guard.js +677 -0
  66. package/dist/environment/application/health.d.ts +143 -0
  67. package/dist/environment/application/health.js +385 -0
  68. package/dist/environment/application/lifecycle-context.d.ts +235 -0
  69. package/dist/environment/application/lifecycle-context.js +154 -0
  70. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  71. package/dist/environment/application/lifecycle-state.js +202 -0
  72. package/dist/environment/application/lifecycle.d.ts +18 -0
  73. package/dist/environment/application/lifecycle.js +737 -0
  74. package/dist/environment/application/logs.d.ts +45 -0
  75. package/dist/environment/application/logs.js +127 -0
  76. package/dist/environment/application/services.d.ts +60 -0
  77. package/dist/environment/application/services.js +89 -0
  78. package/dist/environment/application/setup.d.ts +129 -0
  79. package/dist/environment/application/setup.js +741 -0
  80. package/dist/environment/application/shared.d.ts +11 -0
  81. package/dist/environment/application/shared.js +61 -0
  82. package/dist/environment/application/siblings.d.ts +43 -0
  83. package/dist/environment/application/siblings.js +269 -0
  84. package/dist/environment/claims.d.ts +389 -0
  85. package/dist/environment/claims.js +1701 -0
  86. package/dist/environment/codex-hook-trust.d.ts +11 -0
  87. package/dist/environment/codex-hook-trust.js +185 -0
  88. package/dist/environment/compose-environment.d.ts +1 -0
  89. package/dist/environment/compose-environment.js +6 -0
  90. package/dist/environment/compose-ownership.d.ts +21 -0
  91. package/dist/environment/compose-ownership.js +117 -0
  92. package/dist/environment/compose-topology.d.ts +125 -0
  93. package/dist/environment/compose-topology.js +490 -0
  94. package/dist/environment/contract.d.ts +522 -0
  95. package/dist/environment/contract.js +794 -0
  96. package/dist/environment/declaration.d.ts +14 -0
  97. package/dist/environment/declaration.js +39 -0
  98. package/dist/environment/destructive-composition.d.ts +29 -0
  99. package/dist/environment/destructive-composition.js +333 -0
  100. package/dist/environment/docker-daemon.d.ts +30 -0
  101. package/dist/environment/docker-daemon.js +78 -0
  102. package/dist/environment/docker-endpoint.d.ts +10 -0
  103. package/dist/environment/docker-endpoint.js +19 -0
  104. package/dist/environment/env-schema-observation.d.ts +46 -0
  105. package/dist/environment/env-schema-observation.js +241 -0
  106. package/dist/environment/evidence.d.ts +136 -0
  107. package/dist/environment/evidence.js +437 -0
  108. package/dist/environment/executable.d.ts +38 -0
  109. package/dist/environment/executable.js +190 -0
  110. package/dist/environment/generated-application/authorities.d.ts +5 -0
  111. package/dist/environment/generated-application/authorities.js +61 -0
  112. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  113. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  114. package/dist/environment/generated-application/check.d.ts +13 -0
  115. package/dist/environment/generated-application/check.js +191 -0
  116. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  117. package/dist/environment/generated-application/custom-surface.js +289 -0
  118. package/dist/environment/generated-application/prepare.d.ts +29 -0
  119. package/dist/environment/generated-application/prepare.js +278 -0
  120. package/dist/environment/generated-application/publication.d.ts +33 -0
  121. package/dist/environment/generated-application/publication.js +547 -0
  122. package/dist/environment/generated-application/shared.d.ts +492 -0
  123. package/dist/environment/generated-application/shared.js +354 -0
  124. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  125. package/dist/environment/generated-application/surface-observation.js +69 -0
  126. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  127. package/dist/environment/generated-application/surface-outputs.js +400 -0
  128. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  129. package/dist/environment/generated-application/surfaces.js +704 -0
  130. package/dist/environment/generated-application/sync.d.ts +6 -0
  131. package/dist/environment/generated-application/sync.js +124 -0
  132. package/dist/environment/git-observation.d.ts +202 -0
  133. package/dist/environment/git-observation.js +553 -0
  134. package/dist/environment/health-composition.d.ts +12 -0
  135. package/dist/environment/health-composition.js +645 -0
  136. package/dist/environment/host-observation.d.ts +56 -0
  137. package/dist/environment/host-observation.js +52 -0
  138. package/dist/environment/identity.d.ts +48 -0
  139. package/dist/environment/identity.js +85 -0
  140. package/dist/environment/init-application.d.ts +333 -0
  141. package/dist/environment/init-application.js +726 -0
  142. package/dist/environment/install-policy-observation.d.ts +71 -0
  143. package/dist/environment/install-policy-observation.js +284 -0
  144. package/dist/environment/install-policy.d.ts +48 -0
  145. package/dist/environment/install-policy.js +96 -0
  146. package/dist/environment/instance.d.ts +33 -0
  147. package/dist/environment/instance.js +122 -0
  148. package/dist/environment/instruction-pointer.d.ts +19 -0
  149. package/dist/environment/instruction-pointer.js +144 -0
  150. package/dist/environment/lifecycle-composition.d.ts +31 -0
  151. package/dist/environment/lifecycle-composition.js +742 -0
  152. package/dist/environment/lifecycle-stage.d.ts +120 -0
  153. package/dist/environment/lifecycle-stage.js +408 -0
  154. package/dist/environment/lifecycle.d.ts +75 -0
  155. package/dist/environment/lifecycle.js +354 -0
  156. package/dist/environment/logs.d.ts +132 -0
  157. package/dist/environment/logs.js +519 -0
  158. package/dist/environment/map-limit.d.ts +2 -0
  159. package/dist/environment/map-limit.js +13 -0
  160. package/dist/environment/mcp-observation.d.ts +9 -0
  161. package/dist/environment/mcp-observation.js +46 -0
  162. package/dist/environment/observations.d.ts +46 -0
  163. package/dist/environment/observations.js +346 -0
  164. package/dist/environment/operator-application.d.ts +79 -0
  165. package/dist/environment/operator-application.js +197 -0
  166. package/dist/environment/ports.d.ts +182 -0
  167. package/dist/environment/ports.js +1205 -0
  168. package/dist/environment/posture-observation.d.ts +68 -0
  169. package/dist/environment/posture-observation.js +253 -0
  170. package/dist/environment/presence.d.ts +4 -0
  171. package/dist/environment/presence.js +26 -0
  172. package/dist/environment/read-composition.d.ts +32 -0
  173. package/dist/environment/read-composition.js +397 -0
  174. package/dist/environment/readiness-composition.d.ts +57 -0
  175. package/dist/environment/readiness-composition.js +151 -0
  176. package/dist/environment/reclaim.d.ts +370 -0
  177. package/dist/environment/reclaim.js +1940 -0
  178. package/dist/environment/repository-identity.d.ts +13 -0
  179. package/dist/environment/repository-identity.js +108 -0
  180. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  181. package/dist/environment/runtime-measurement-record.js +111 -0
  182. package/dist/environment/runtime-measurement.d.ts +27 -0
  183. package/dist/environment/runtime-measurement.js +224 -0
  184. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  185. package/dist/environment/runtime-probe-residue.js +148 -0
  186. package/dist/environment/runtime-probe.d.ts +72 -0
  187. package/dist/environment/runtime-probe.js +725 -0
  188. package/dist/environment/runtime.d.ts +551 -0
  189. package/dist/environment/runtime.js +2405 -0
  190. package/dist/environment/seed.d.ts +16 -0
  191. package/dist/environment/seed.js +43 -0
  192. package/dist/environment/selftest-application.d.ts +86 -0
  193. package/dist/environment/selftest-application.js +713 -0
  194. package/dist/environment/sensitive.d.ts +3 -0
  195. package/dist/environment/sensitive.js +48 -0
  196. package/dist/environment/served-checkout-grants.d.ts +53 -0
  197. package/dist/environment/served-checkout-grants.js +182 -0
  198. package/dist/environment/setup-observation.d.ts +62 -0
  199. package/dist/environment/setup-observation.js +1473 -0
  200. package/dist/environment/setup.d.ts +43 -0
  201. package/dist/environment/setup.js +176 -0
  202. package/dist/environment/sibling-checkouts.d.ts +119 -0
  203. package/dist/environment/sibling-checkouts.js +254 -0
  204. package/dist/environment/sibling-instructions.d.ts +33 -0
  205. package/dist/environment/sibling-instructions.js +224 -0
  206. package/dist/environment/sibling-observation.d.ts +27 -0
  207. package/dist/environment/sibling-observation.js +45 -0
  208. package/dist/environment/sibling-pull.d.ts +23 -0
  209. package/dist/environment/sibling-pull.js +95 -0
  210. package/dist/environment/sibling-registration.d.ts +45 -0
  211. package/dist/environment/sibling-registration.js +538 -0
  212. package/dist/environment/stack-inputs.d.ts +18 -0
  213. package/dist/environment/stack-inputs.js +210 -0
  214. package/dist/environment/stack-inventory-prune.d.ts +31 -0
  215. package/dist/environment/stack-inventory-prune.js +129 -0
  216. package/dist/environment/stack-inventory-report.d.ts +44 -0
  217. package/dist/environment/stack-inventory-report.js +78 -0
  218. package/dist/environment/stack-inventory.d.ts +94 -0
  219. package/dist/environment/stack-inventory.js +418 -0
  220. package/dist/environment/state.d.ts +410 -0
  221. package/dist/environment/state.js +2153 -0
  222. package/dist/environment/templates.d.ts +140 -0
  223. package/dist/environment/templates.js +685 -0
  224. package/dist/environment/tool-registration.d.ts +9 -0
  225. package/dist/environment/tool-registration.js +92 -0
  226. package/dist/environment/upgrade-application.d.ts +19 -0
  227. package/dist/environment/upgrade-application.js +175 -0
  228. package/dist/environment-exec/execute.d.ts +105 -0
  229. package/dist/environment-exec/execute.js +1179 -0
  230. package/dist/environment-exec/libexec-program.d.ts +6 -0
  231. package/dist/environment-exec/libexec-program.js +22 -0
  232. package/dist/environment-exec/verified-digest.d.ts +4 -0
  233. package/dist/environment-exec/verified-digest.js +16 -0
  234. package/dist/exact-keys.d.ts +7 -0
  235. package/dist/exact-keys.js +15 -0
  236. package/dist/fix-catalogue.d.ts +32 -0
  237. package/dist/fix-catalogue.js +60 -0
  238. package/dist/freshness.d.ts +29 -0
  239. package/dist/freshness.js +75 -0
  240. package/dist/generated-environment/adopt.d.ts +16 -0
  241. package/dist/generated-environment/adopt.js +271 -0
  242. package/dist/generated-environment/command.d.ts +47 -0
  243. package/dist/generated-environment/command.js +75 -0
  244. package/dist/generated-environment/transaction.d.ts +186 -0
  245. package/dist/generated-environment/transaction.js +1770 -0
  246. package/dist/grading/evaluate.d.ts +71 -0
  247. package/dist/grading/evaluate.js +403 -0
  248. package/dist/grading/grade.d.ts +33 -0
  249. package/dist/grading/grade.js +57 -0
  250. package/dist/guards/answer-confirmation.d.ts +41 -0
  251. package/dist/guards/answer-confirmation.js +109 -0
  252. package/dist/guards/evaluator.d.ts +130 -0
  253. package/dist/guards/evaluator.js +1258 -0
  254. package/dist/guards/harness-adapter.d.ts +17 -0
  255. package/dist/guards/harness-adapter.js +57 -0
  256. package/dist/guards/host.d.ts +8 -0
  257. package/dist/guards/host.js +10 -0
  258. package/dist/guards/invocation-parser.d.ts +66 -0
  259. package/dist/guards/invocation-parser.js +595 -0
  260. package/dist/guards/local-identity.d.ts +44 -0
  261. package/dist/guards/local-identity.js +129 -0
  262. package/dist/guards/policy-change.d.ts +43 -0
  263. package/dist/guards/policy-change.js +159 -0
  264. package/dist/guards/policy-pin.d.ts +13 -0
  265. package/dist/guards/policy-pin.js +177 -0
  266. package/dist/guards/protected-locations.d.ts +62 -0
  267. package/dist/guards/protected-locations.js +461 -0
  268. package/dist/guards/read-only.d.ts +37 -0
  269. package/dist/guards/read-only.js +315 -0
  270. package/dist/init/command.d.ts +41 -0
  271. package/dist/init/command.js +330 -0
  272. package/dist/init/derive.d.ts +53 -0
  273. package/dist/init/derive.js +819 -0
  274. package/dist/init/draft.d.ts +140 -0
  275. package/dist/init/draft.js +1228 -0
  276. package/dist/init/render.d.ts +47 -0
  277. package/dist/init/render.js +104 -0
  278. package/dist/init/scaffold.d.ts +53 -0
  279. package/dist/init/scaffold.js +121 -0
  280. package/dist/invocation.d.ts +13 -0
  281. package/dist/invocation.js +20 -0
  282. package/dist/package-metadata.d.ts +9 -0
  283. package/dist/package-metadata.js +19 -0
  284. package/dist/readiness.d.ts +53 -0
  285. package/dist/readiness.js +88 -0
  286. package/dist/references/named.d.ts +24 -0
  287. package/dist/references/named.js +27 -0
  288. package/dist/refusal.d.ts +15 -0
  289. package/dist/refusal.js +7 -0
  290. package/dist/registry-derivation.d.ts +31 -0
  291. package/dist/registry-derivation.js +32 -0
  292. package/dist/registry-resolve/command.d.ts +47 -0
  293. package/dist/registry-resolve/command.js +226 -0
  294. package/dist/registry-resolve/registry.d.ts +68 -0
  295. package/dist/registry-resolve/registry.js +292 -0
  296. package/dist/registry-resolve/render-shell.d.ts +18 -0
  297. package/dist/registry-resolve/render-shell.js +36 -0
  298. package/dist/repair.d.ts +29 -0
  299. package/dist/repair.js +8 -0
  300. package/dist/repository-path.d.ts +9 -0
  301. package/dist/repository-path.js +16 -0
  302. package/dist/schema-json.d.ts +1 -0
  303. package/dist/schema-json.js +177 -0
  304. package/dist/schema.d.ts +1289 -0
  305. package/dist/schema.js +985 -0
  306. package/dist/setup/command.d.ts +4 -0
  307. package/dist/setup/command.js +4 -0
  308. package/dist/sha256.d.ts +1 -0
  309. package/dist/sha256.js +4 -0
  310. package/dist/surfaces/adoption.d.ts +41 -0
  311. package/dist/surfaces/adoption.js +217 -0
  312. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  313. package/dist/surfaces/consumer-instructions.js +43 -0
  314. package/dist/surfaces/contract.d.ts +42 -0
  315. package/dist/surfaces/contract.js +169 -0
  316. package/dist/surfaces/core-declarations.d.ts +24 -0
  317. package/dist/surfaces/core-declarations.js +79 -0
  318. package/dist/surfaces/development-markers.d.ts +1 -0
  319. package/dist/surfaces/development-markers.js +18 -0
  320. package/dist/surfaces/generator-adapter.d.ts +71 -0
  321. package/dist/surfaces/generator-adapter.js +809 -0
  322. package/dist/surfaces/harness-registry.d.ts +260 -0
  323. package/dist/surfaces/harness-registry.js +146 -0
  324. package/dist/surfaces/mcp.d.ts +145 -0
  325. package/dist/surfaces/mcp.js +349 -0
  326. package/dist/surfaces/merge.d.ts +30 -0
  327. package/dist/surfaces/merge.js +105 -0
  328. package/dist/surfaces/ownership.d.ts +190 -0
  329. package/dist/surfaces/ownership.js +795 -0
  330. package/dist/surfaces/projection.d.ts +136 -0
  331. package/dist/surfaces/projection.js +716 -0
  332. package/dist/surfaces/render.d.ts +79 -0
  333. package/dist/surfaces/render.js +636 -0
  334. package/dist/surfaces/restrictions.d.ts +130 -0
  335. package/dist/surfaces/restrictions.js +447 -0
  336. package/dist/surfaces/selftest.d.ts +162 -0
  337. package/dist/surfaces/selftest.js +393 -0
  338. package/dist/surfaces/skill-contract.d.ts +85 -0
  339. package/dist/surfaces/skill-contract.js +449 -0
  340. package/dist/surfaces/sync.d.ts +57 -0
  341. package/dist/surfaces/sync.js +164 -0
  342. package/dist/team-metadata.d.ts +41 -0
  343. package/dist/team-metadata.js +513 -0
  344. package/dist/testing/gates.d.ts +91 -0
  345. package/dist/testing/gates.js +182 -0
  346. package/dist/testing/hooks.d.ts +59 -0
  347. package/dist/testing/hooks.js +139 -0
  348. package/dist/testing/test-protection.d.ts +34 -0
  349. package/dist/testing/test-protection.js +137 -0
  350. package/dist/testing/verdict.d.ts +29 -0
  351. package/dist/testing/verdict.js +62 -0
  352. package/dist/testing/weakening.d.ts +15 -0
  353. package/dist/testing/weakening.js +30 -0
  354. package/dist/work-orders/application-closure.d.ts +7 -0
  355. package/dist/work-orders/application-closure.js +76 -0
  356. package/dist/work-orders/application-context.d.ts +162 -0
  357. package/dist/work-orders/application-context.js +137 -0
  358. package/dist/work-orders/application-decisions.d.ts +27 -0
  359. package/dist/work-orders/application-decisions.js +121 -0
  360. package/dist/work-orders/application-grading.d.ts +11 -0
  361. package/dist/work-orders/application-grading.js +212 -0
  362. package/dist/work-orders/application.d.ts +6 -0
  363. package/dist/work-orders/application.js +223 -0
  364. package/dist/work-orders/composition.d.ts +101 -0
  365. package/dist/work-orders/composition.js +1202 -0
  366. package/dist/work-orders/contract.d.ts +77 -0
  367. package/dist/work-orders/contract.js +140 -0
  368. package/dist/work-orders/decisions.d.ts +23 -0
  369. package/dist/work-orders/decisions.js +55 -0
  370. package/dist/work-orders/store.d.ts +207 -0
  371. package/dist/work-orders/store.js +291 -0
  372. package/docs/README.md +68 -0
  373. package/docs/architecture.md +136 -0
  374. package/docs/capability-matrix.md +58 -0
  375. package/docs/cli-reference.md +807 -0
  376. package/docs/command-list.md +62 -0
  377. package/docs/compose-patterns.md +99 -0
  378. package/docs/concepts.md +173 -0
  379. package/docs/configuration-essentials.md +177 -0
  380. package/docs/configuration-reference.md +636 -0
  381. package/docs/docs-check.md +76 -0
  382. package/docs/environment.md +948 -0
  383. package/docs/extending.md +43 -0
  384. package/docs/faq.md +113 -0
  385. package/docs/getting-started.md +692 -0
  386. package/docs/harness-surfaces.md +500 -0
  387. package/docs/joining-a-team.md +76 -0
  388. package/docs/release-policy.md +49 -0
  389. package/docs/safety-and-autonomy.md +463 -0
  390. package/docs/troubleshooting.md +382 -0
  391. package/docs/tutorial.md +171 -0
  392. package/docs/work-and-browser.md +420 -0
  393. package/libexec/browser-operation.cjs +270 -0
  394. package/libexec/fence-marker.cjs +101 -0
  395. package/libexec/fence-marker.d.cts +24 -0
  396. package/libexec/fenced-group-runner.cjs +77 -0
  397. package/libexec/fenced-supervisor.cjs +137 -0
  398. package/libexec/generated-filesystem.cjs +731 -0
  399. package/libexec/install-filesystem.cjs +225 -0
  400. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  401. package/libexec/rulesync-generator.cjs +743 -0
  402. package/libexec/surface-generator-worker.cjs +266 -0
  403. package/libexec/surface-output-reader.cjs +99 -0
  404. package/libexec/surface-scratch-cleanup.cjs +14 -0
  405. package/libexec/validate-harness-evidence.d.mts +183 -0
  406. package/libexec/validate-harness-evidence.mjs +1312 -0
  407. package/package.json +74 -4
  408. package/surfaces/README.md +33 -0
  409. package/surfaces/agents/evaluator.agent.md +76 -0
  410. package/surfaces/guards/bash-guard.sh +48 -0
  411. package/surfaces/guards/claude.adapter.json +25 -0
  412. package/surfaces/guards/codex.adapter.json +24 -0
  413. package/surfaces/guards/opencode.adapter.json +25 -0
  414. package/surfaces/references/browser.md +63 -0
  415. package/surfaces/references/test-data-and-personas.md +84 -0
  416. package/surfaces/references/testing-tiers.md +11 -0
  417. package/surfaces/skills/cockpit-browser.skill.md +59 -0
  418. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  419. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  420. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  421. package/surfaces/skills/cockpit-work.skill.md +34 -0
  422. package/surfaces/skills/collection.json +5 -0
  423. package/surfaces/skills/procedures/explain.md +66 -0
  424. package/surfaces/skills/procedures/fix.md +95 -0
  425. package/surfaces/skills/procedures/setup.md +452 -0
  426. package/surfaces/skills/procedures/work.md +134 -0
@@ -0,0 +1,212 @@
1
+ // Grading: the standing criteria cockpit adds to every grade, the test protection a grade and a test
2
+ // run apply, `work grade` against an order's accepted contract and `grade --criteria` with no order.
3
+ import { posix } from 'node:path';
4
+ import { RefusalError } from '../environment/contract.js';
5
+ import { runGate } from '../testing/gates.js';
6
+ import { describeWeakening, testWeakeningDigest } from '../testing/weakening.js';
7
+ import { scopeMatcher } from './contract.js';
8
+ import { complete, now, refusal, TERMINAL, unsatisfied } from './application-context.js';
9
+ import { decisions } from './application-decisions.js';
10
+ // A list in a criterion's text stays readable; the full list is in the grade's result beside it.
11
+ const listed = (rows, separator, limit = 3_000) => {
12
+ const text = rows.join(separator);
13
+ return text.length <= limit ? text : `${text.slice(0, limit)} (cut; ${rows.length} in all)`;
14
+ };
15
+ const weakeningPath = (row) => row.repository === '.' ? row.path : `${row.repository}:${row.path}`;
16
+ // The agent's contract amendments since a person or CI last accepted it, each still to be judged by the next grade.
17
+ function amendmentsToReview(record) {
18
+ const reviewed = record.amendments.findLastIndex(row => row.by !== 'agent');
19
+ return record.amendments.slice(reviewed + 1).flatMap(row => row.review);
20
+ }
21
+ // The paths a self-reported note does not name, alone or by a directory above them.
22
+ const unaddressed = (note, paths) => paths.filter(path => {
23
+ const parts = path.split('/');
24
+ return !parts.some((_, index) => note.includes(index === parts.length - 1 ? path : `${parts.slice(0, index + 1).join('/')}/`));
25
+ });
26
+ const ACKNOWLEDGE = 'npx --no cockpit work acknowledge --config <your cockpit.toml> --work-order <id> --note <why this is not a weakening>';
27
+ // Tests weakened since the base, judged by rule where no model reads the change. An acknowledgement
28
+ // covers exactly the findings it was recorded for, so any further weakening fails again.
29
+ function protection(weak, record, gate) {
30
+ const rows = weak.findings.map(describeWeakening);
31
+ const digest = testWeakeningDigest(weak.findings);
32
+ const acknowledged = weak.findings.length ? record?.acknowledgements.find(row => row.digest === digest) : undefined;
33
+ const how = !record ? 'a work order lets it be acknowledged; there is none for this grade'
34
+ : gate === 'person' ? `a person records it with ${ACKNOWLEDGE} --interactive` : gate === 'ci' ? `acknowledge it with ${ACKNOWLEDGE}, which the work verify job confirms in CI`
35
+ : `acknowledge it with ${ACKNOWLEDGE}`;
36
+ const verdict = !weak.findings.length ? undefined
37
+ : acknowledged ? { verdict: 'met', note: `weakening acknowledged by ${acknowledged.by}: ${acknowledged.note}`.slice(0, 4_000) }
38
+ : { verdict: 'unmet', note: `tests were weakened since the base (${rows.join('; ')}; test files from ${weak.patterns.source}); revert it, or ${how}`.slice(0, 4_000) };
39
+ return { verdict, value: { testFiles: weak.patterns.source, findings: rows, ...acknowledged ? { acknowledgedBy: acknowledged.by } : {} } };
40
+ }
41
+ const TESTS_JUSTIFIED = 'test-changes-justified';
42
+ const AMENDMENTS_HOLD = 'amendments-hold-bar';
43
+ const STANDING = [
44
+ { id: 'decisions-recorded', text: ({ docs, record }) => {
45
+ if (!docs?.decisions || !record)
46
+ return undefined;
47
+ const recorded = decisions(record).map(row => `${row.decision} (${row.title})${row.resolution?.record ? ` in ${row.resolution.record}` : ''}`).join('; ');
48
+ return ('Every lasting decision this change makes (a choice a later change must know about, such as a format, a boundary, '
49
+ + `a dependency or a rejected option) is recorded on the work order or in a decision record under ${docs.decisions}. Recorded on this order: ${recorded || 'none'}.`).slice(0, 2_000);
50
+ } },
51
+ { id: 'docs-updated', text: ({ docs }) => docs ? 'Documentation that describes behaviour this change alters is updated in the change, or no documentation describes it.' : undefined },
52
+ // Which paths changed outside scope is a git fact; whether each change was needed is a judgement.
53
+ { id: 'scope-changes-necessary', text: ({ outOfScope }) => outOfScope.length ? 'Each change outside the scope is necessary to the objective and harmless, such as a lockfile, a regenerated '
54
+ + 'reference or a changeset the in-scope change requires; any other is scope creep and fails this. The note names each path and says why. '
55
+ + `Changed outside scope: ${listed(outOfScope, ', ')}.` : undefined },
56
+ // The detector names candidates; whether each change keeps the tests' force is a judgement.
57
+ { id: TESTS_JUSTIFIED, weakening: true, text: ({ weak, record }) => {
58
+ if (!weak.findings.length)
59
+ return undefined;
60
+ const acknowledged = record?.acknowledgements.map(row => `${row.by}: ${row.note}`) ?? [];
61
+ return 'Each test change flagged below keeps the tests\' force: a removed or loosened test is replaced by one at least '
62
+ + 'as strict, or tested behaviour the change itself removes. A test weakened to make the change pass fails this. The note names each file and says why. '
63
+ + `Flagged (test files from ${weak.patterns.source}): ${listed(weak.findings.map(describeWeakening), '; ')}. `
64
+ + `Acknowledgements recorded on the order: ${acknowledged.length ? listed(acknowledged, '; ', 1_000) : 'none'}.`;
65
+ }, underChecks: { id: 'test-protection', verdict: ({ weak, record, acknowledge }) => protection(weak, record, acknowledge).verdict } },
66
+ // An agent may reword its own contract; whether the rewording lowers the bar is a judgement. Under
67
+ // checks nothing could judge it, so the amend gate passes such an amendment to a person instead.
68
+ { id: AMENDMENTS_HOLD, text: ({ amendments }) => amendments.length ? 'The amendments an agent made to the accepted contract do not lower the bar the work is held to: each clarifies or '
69
+ + 'tightens what it replaces, and none excuses work the original asked for. Before and after: '
70
+ + `${listed(amendments.map(row => `${row.field}: "${row.before}" -> "${row.after}"`), '; ')}.` : undefined,
71
+ remedy: id => `; the next amendment of ${id} is a person's: revert or rewrite workorders/${id}.md, then a person runs npx --no cockpit work accept --config <your cockpit.toml> --work-order ${id} --interactive` },
72
+ ];
73
+ const RESERVED = new Set(STANDING.map(row => row.id));
74
+ const standingCriteria = (input) => STANDING.flatMap(row => {
75
+ const text = row.text(input);
76
+ return text === undefined ? [] : [{ id: row.id, text }];
77
+ });
78
+ const standingVerdictsUnderChecks = (input) => STANDING.flatMap(row => {
79
+ const reached = row.underChecks?.verdict(input);
80
+ return reached ? [{ id: row.underChecks.id, ...reached }] : [];
81
+ });
82
+ const WEAKENING_IDS = new Set(STANDING.flatMap(row => row.weakening ? [row.id, ...row.underChecks ? [row.underChecks.id] : []] : []));
83
+ const remedyOf = (id, orderId) => STANDING.find(row => row.id === id)?.remedy?.(orderId) ?? '';
84
+ const split = (verdicts) => ({ others: verdicts.filter(row => row.verdict !== 'met' && !WEAKENING_IDS.has(row.id)),
85
+ weakening: verdicts.filter(row => WEAKENING_IDS.has(row.id) && row.verdict !== 'met').map(row => `${row.id} ${row.verdict}: ${row.note}`).join('; ') || undefined });
86
+ // A test run's result, held to the protection of every open order.
87
+ export async function testRun(captured, composition) {
88
+ const operation = captured.operation;
89
+ const prepared = await composition.gate(captured);
90
+ const run = await runGate(prepared.registry, prepared.request, prepared.dependencies);
91
+ // A passing run proves nothing if the tests it ran were weakened, so it is checked against the base of each open order.
92
+ // Under checks grading an unacknowledged finding fails the run; where a model grades, the grade judges it.
93
+ const open = (await composition.store.list()).filter(row => !TERMINAL.has(row.state));
94
+ // Orders accepted at the same commits share one observation of what changed since.
95
+ const observed = new Map();
96
+ const weakeningSince = (base) => {
97
+ const key = JSON.stringify(Object.entries(base).sort(([left], [right]) => left < right ? -1 : 1));
98
+ if (!observed.has(key))
99
+ observed.set(key, composition.testWeakening(base));
100
+ return observed.get(key);
101
+ };
102
+ const guards = await Promise.all(open.map(async (row) => ({ id: row.id, ...protection(await weakeningSince(row.accepted.base), row, composition.policy.gates.acknowledge) })));
103
+ const binding = composition.policy.grading === 'checks';
104
+ const testProtection = guards.length ? { comparedWith: guards.map(row => `${row.id}'s base`), orders: guards.map(row => ({ id: row.id, ...row.value })),
105
+ ...!binding && guards.some(row => row.value.findings.length) ? { note: `under [work] grading = "${composition.policy.grading}" each order's grade judges these findings as ${TESTS_JUSTIFIED}` } : {} }
106
+ : { comparedWith: [], note: 'no open work order records a base commit to compare tests with; accept an order before changing tests' };
107
+ const weakening = binding ? guards.flatMap(row => row.verdict?.verdict === 'unmet' ? [`${row.id}: ${row.verdict.note}`] : []).join(' | ') : '';
108
+ const failure = run.satisfiesGate ? '' : `${run.verdict}: ${run.reasons.join('; ') || 'declared gate was not satisfied'}`;
109
+ return !failure && !weakening ? complete(operation, { ...run, testProtection }) : unsatisfied(operation, { ...run, testProtection }, failure, weakening || undefined);
110
+ }
111
+ export async function gradeOrder(request, context) {
112
+ const { composition, operation, policy, store, current, runChecks } = context;
113
+ const { order, record } = await current(request.workOrderId);
114
+ if (policy.grading === 'self' ? !request.verdict || !request.note : request.verdict !== undefined || request.note !== undefined) {
115
+ return refusal(operation, policy.grading === 'self' ? '[work] grading = "self": pass --verdict met|unmet and --note <what you checked>'
116
+ : `--verdict and --note apply only under [work] grading = "self"; this cockpit grades with ${policy.grading}`);
117
+ }
118
+ let verdicts;
119
+ let evaluator;
120
+ const unchecked = order.criteria.filter(criterion => !criterion.check).map(criterion => criterion.id);
121
+ if (policy.grading === 'checks' && unchecked.length)
122
+ return refusal(operation, `[work] grading = "checks" closes an order only when cockpit ran a check for every criterion; ${unchecked.join(', ')} declare none`);
123
+ const judged = policy.grading !== 'checks';
124
+ const taken = judged ? order.criteria.filter(criterion => RESERVED.has(criterion.id)).map(criterion => criterion.id) : [];
125
+ if (taken.length)
126
+ return refusal(operation, `${order.id} names a criterion ${taken.join(', ')}, which cockpit adds to grades itself; rename it in workorders/${order.id}.md`);
127
+ // Checks bind in every mode, so an agent's own `met` never covers a failed check. Changes are read
128
+ // after the checks, so files a check writes count too.
129
+ const checks = await runChecks(order);
130
+ const { changes, weakening: weak } = await composition.review(record.accepted.base);
131
+ // The decision records an order is required to write are part of its work wherever scope points.
132
+ const decisionFiles = [composition.docs?.decisions, composition.docs?.decisionsIndex].filter((path) => path !== undefined);
133
+ const inScope = scopeMatcher([...order.scope, ...decisionFiles]);
134
+ const outOfScope = changes.map(change => change.path).filter(path => !inScope(path));
135
+ const guarded = protection(weak, record, policy.gates.acknowledge);
136
+ const amendments = judged ? amendmentsToReview(record) : [];
137
+ const standingInput = { docs: composition.docs, record, outOfScope, weak, amendments, acknowledge: policy.gates.acknowledge };
138
+ const standing = judged ? standingCriteria(standingInput) : [];
139
+ if (policy.grading === 'self') {
140
+ // The agent's verdict covers the standing criteria too, so its note must at least name what they list.
141
+ const missing = unaddressed(request.note, [...outOfScope, ...weak.findings.map(weakeningPath)]);
142
+ if (missing.length)
143
+ return refusal(operation, `[work] grading = "self": --note must name each path changed outside scope and each flagged test file, or a directory above it, `
144
+ + `and say why its change is needed; it does not name ${listed(missing, ', ', 1_000)}`);
145
+ verdicts = [...order.criteria, ...standing].map(criterion => ({ id: criterion.id, verdict: request.verdict, note: request.note }));
146
+ }
147
+ else if (policy.grading === 'checks') {
148
+ verdicts = order.criteria.map(criterion => {
149
+ const result = checks.find(row => row.criterion === criterion.id);
150
+ return { id: criterion.id, verdict: result.passed ? 'met' : 'unmet', note: `cockpit ran the check: ${result.outcome}` };
151
+ });
152
+ // No model reads the change under checks, so weakened tests fail the grade until acknowledged.
153
+ verdicts = [...verdicts, ...standingVerdictsUnderChecks(standingInput)];
154
+ }
155
+ else {
156
+ // The contract's own change is shown to the evaluator as a changed file, so a verdict on the amendments can quote it.
157
+ const contract = amendments.length ? [{ path: `workorders/${order.id}.md`, status: 'amended',
158
+ diff: amendments.map(row => `${row.field}\n- ${row.before}\n+ ${row.after}`).join('\n') }] : [];
159
+ const graded = await composition.evaluate({ objective: order.objective, criteria: [...order.criteria, ...standing], nonGoals: order.non_goals, scope: order.scope,
160
+ checks, changes: [...changes, ...contract], outOfScope });
161
+ if ('refusal' in graded)
162
+ return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
163
+ verdicts = graded.criteria;
164
+ evaluator = { tool: graded.evaluator.tool, fallbacks: graded.evaluator.fallbacks.map(row => ({ ...row })) };
165
+ }
166
+ // Where a model grades, whether a change outside scope was needed is its standing criterion; under checks nothing can judge it, so any fails.
167
+ const passed = verdicts.every(row => row.verdict === 'met') && checks.every(row => row.passed) && (judged || outOfScope.length === 0);
168
+ const grade = { at: now(), digest: record.accepted.digest, mode: policy.grading, passed,
169
+ criteria: [...verdicts], outOfScope: [...outOfScope], ...evaluator ? { evaluator } : {}, ...request.note ? { note: request.note } : {} };
170
+ const loweredBar = verdicts.find(row => row.id === AMENDMENTS_HOLD && row.verdict !== 'met');
171
+ await store.update(order.id, async (existing) => {
172
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
173
+ throw new RefusalError(`${order.id} changed while it was graded; rerun work grade`);
174
+ return { record: { ...existing, state: passed ? 'graded' : 'accepted', attempts: existing.attempts + (passed ? 0 : 1), grade,
175
+ ...loweredBar ? { amendmentsFailedReview: { at: grade.at, note: loweredBar.note } } : {},
176
+ ...checks.length ? { checks: { at: now(), digest: record.accepted.digest, results: [...checks] } } : {} }, value: undefined };
177
+ });
178
+ const value = { id: order.id, state: passed ? 'graded' : 'accepted', mode: policy.grading, criteria: verdicts, checks, outOfScope, testProtection: guarded.value, ...evaluator ? { evaluator } : {} };
179
+ if (passed)
180
+ return complete(operation, value);
181
+ return unsatisfied(operation, value, [...split(verdicts).others.map(row => `${row.id} ${row.verdict}: ${row.note}${remedyOf(row.id, order.id)}`),
182
+ ...checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`),
183
+ ...!judged && outOfScope.length ? [`changed outside scope: ${outOfScope.join(', ')}`] : []].join('; '), split(verdicts).weakening);
184
+ }
185
+ // The quick path: an independent grade of uncommitted work with no order and no record.
186
+ export async function gradeCriteriaFile(request, context) {
187
+ const { composition, operation, policy, runChecks } = context;
188
+ const parsed = await composition.readCriteriaFile(request.criteriaFile);
189
+ if (!parsed.ok)
190
+ return refusal(operation, `${request.criteriaFile} is not a criteria file: ${parsed.problems.join('; ')}`);
191
+ const file = parsed.value;
192
+ const taken = file.criteria.filter(criterion => RESERVED.has(criterion.id)).map(criterion => criterion.id);
193
+ if (taken.length)
194
+ return refusal(operation, `${request.criteriaFile} names a criterion ${taken.join(', ')}, which cockpit adds to grades itself; rename it`);
195
+ const checks = await runChecks(file);
196
+ // The criteria file is the request, not part of the change it grades.
197
+ // With no order there is no recorded base, so the change since HEAD is compared, and the evaluator judges any test weakening.
198
+ const review = await composition.review(await composition.heads());
199
+ const changes = review.changes.filter(change => change.path !== posix.normalize(request.criteriaFile));
200
+ const inScope = scopeMatcher(file.scope);
201
+ const outOfScope = file.scope.length ? changes.map(change => change.path).filter(path => !inScope(path)) : [];
202
+ const weak = review.weakening;
203
+ const criteria = [...file.criteria, ...standingCriteria({ outOfScope, weak, amendments: [], acknowledge: policy.gates.acknowledge })];
204
+ const subject = { ...file.objective ? { objective: file.objective } : {}, criteria, nonGoals: [], scope: file.scope, checks, changes, outOfScope };
205
+ const graded = await composition.evaluate(subject);
206
+ if ('refusal' in graded)
207
+ return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
208
+ const passed = graded.criteria.every(row => row.verdict === 'met') && checks.every(row => row.passed);
209
+ const value = { criteria: graded.criteria, checks, outOfScope, testProtection: { testFiles: weak.patterns.source, findings: weak.findings.map(describeWeakening) }, evaluator: graded.evaluator };
210
+ return passed ? complete(operation, value) : unsatisfied(operation, value, split(graded.criteria).others.map(row => `${row.id} ${row.verdict}: ${row.note}`)
211
+ .concat(checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`)).join('; '), split(graded.criteria).weakening);
212
+ }
@@ -0,0 +1,6 @@
1
+ import { type BrowserCommandResult } from '../browser/commands.js';
2
+ import { type WorkApplicationComposition } from './composition.js';
3
+ import { type WorkApplicationOutcome, type WorkApplicationRequest } from './application-context.js';
4
+ export { captureWorkApplicationRequest, type WorkApplicationOutcome, type WorkApplicationRequest } from './application-context.js';
5
+ export declare function runWorkApplication(request: WorkApplicationRequest, composition: WorkApplicationComposition): Promise<WorkApplicationOutcome>;
6
+ export declare function browserOutcome(operation: 'browser-drive' | 'browser-screenshot', result: BrowserCommandResult, failOnFindings: boolean): WorkApplicationOutcome;
@@ -0,0 +1,223 @@
1
+ // Owns the work-order lifecycle: draft → accepted → checked → graded → done, with blocked and stale
2
+ // derived from the record and the current file, and partial and abandoned as closures. Each command
3
+ // that passes a gate asks the autonomy policy who may pass it; a person gate returns a continuation
4
+ // the CLI completes only with the digest a person types in a terminal. It also routes the browser and
5
+ // test-gate operations that share the work composition. Decisions, grading and closure are owned by the
6
+ // application-* modules beside it, on the shared steps application-context.ts binds.
7
+ import { executeBrowserCommand } from '../browser/commands.js';
8
+ import { evaluateUiGate } from '../browser/ui-gate.js';
9
+ import { policyWeakening } from '../autonomy.js';
10
+ import { errorCause, RefusalError } from '../environment/contract.js';
11
+ import { describeWeakening, testWeakeningDigest } from '../testing/weakening.js';
12
+ import { checksBindsField, contractAmendment, workOrderDigest } from './contract.js';
13
+ import { isWorkApplicationComposition } from './composition.js';
14
+ import { captureWorkApplicationRequest, complete, displayState, now, openQuestions, refusal, TERMINAL, unsatisfied, workContext } from './application-context.js';
15
+ import { pendingCi, signoff, verify } from './application-closure.js';
16
+ import { decideOrder, DECISIONS_UNDECLARED, relatedDecisions, unresolvedDecisions } from './application-decisions.js';
17
+ import { gradeCriteriaFile, gradeOrder, testRun } from './application-grading.js';
18
+ export { captureWorkApplicationRequest } from './application-context.js';
19
+ export async function runWorkApplication(request, composition) {
20
+ const captured = captureWorkApplicationRequest(request);
21
+ if (!captured)
22
+ return refusal('invalid', 'Work application requests require a closed selector-only shape');
23
+ if (!isWorkApplicationComposition(composition))
24
+ return refusal('invalid', 'An issued work application composition is required');
25
+ const operation = captured.operation;
26
+ try {
27
+ if (captured.operation === 'browser-drive' || captured.operation === 'browser-screenshot') {
28
+ const kind = captured.operation === 'browser-screenshot' ? 'screenshot' : 'drive';
29
+ return browserOutcome(captured.operation, await executeBrowserCommand(await composition.browser(kind, captured.role, captured.route)), captured.failOnFindings === true);
30
+ }
31
+ if (captured.operation === 'test-run')
32
+ return await testRun(captured, composition);
33
+ // A policy weakened in cockpit.toml since the last sync is not in force until a person accepts it there.
34
+ const recorded = await composition.recordedPolicy();
35
+ const weakened = recorded ? policyWeakening(recorded, composition.policy) : [];
36
+ if (weakened.length) {
37
+ return refusal(operation, `cockpit.toml weakens ${weakened.map(row => `${row.field} from ${row.from} to ${row.to}`).join(', ')} since the last sync; `
38
+ + 'a person runs npx --no cockpit sync --config <your cockpit.toml> --interactive and accepts the weakening, or the change is reverted');
39
+ }
40
+ return await work(captured, composition);
41
+ }
42
+ catch (error) {
43
+ return refusal(operation, errorCause(error));
44
+ }
45
+ }
46
+ // Findings are evidence for the agent and the grader to weigh, so they fail the command only for a criterion that demands a clean route.
47
+ export function browserOutcome(operation, result, failOnFindings) {
48
+ if (result.kind === 'refusal')
49
+ return refusal(operation, result.reason);
50
+ const gate = evaluateUiGate({ activating: operation === 'browser-screenshot' ? 'screenshot' : 'drive', receipts: [result.receipt] });
51
+ if (gate.kind === 'refusal')
52
+ return refusal(operation, gate.reason);
53
+ if (failOnFindings && result.receipt.verdict === 'findings')
54
+ return unsatisfied(operation, result, browserFindingsReason(result.receipt));
55
+ return complete(operation, result);
56
+ }
57
+ function browserFindingsReason(receipt) {
58
+ return receipt.surfaces.flatMap(row => {
59
+ const found = row.diagnostics;
60
+ if (!found || row.verdict !== 'findings')
61
+ return [];
62
+ const parts = [[found.consoleErrors, 'console error'], [found.pageErrors, 'uncaught page error'], [found.failedRequests, 'failed request']]
63
+ .flatMap(([count, noun]) => count ? [`${count} ${noun}${count === 1 ? '' : 's'}`] : []);
64
+ return [`${row.name}: ${parts.join(', ')}`];
65
+ }).join('; ');
66
+ }
67
+ async function work(request, composition) {
68
+ const context = workContext(composition, request.operation);
69
+ const { operation, policy, store, observe, wip, gated, current, runChecks } = context;
70
+ if (request.operation === 'work-status') {
71
+ const ids = request.workOrderId ? [request.workOrderId] : [...new Set([...await composition.orderIds(), ...(await store.list()).map(row => row.id)])].sort();
72
+ const rows = await Promise.all(ids.map(observe));
73
+ const closed = new Set(rows.filter(row => row.record?.state === 'done').map(row => row.id));
74
+ return complete(operation, {
75
+ grading: policy.grading, gates: policy.gates, wip: { open: await wip(), cap: composition.caps.wip },
76
+ orders: await Promise.all(rows.map(async (row) => ({
77
+ id: row.id, state: displayState(row, composition.caps.attempts),
78
+ ...row.record?.closure ? { closedAs: row.record.closure.how, closedBy: row.record.closure.by } : {},
79
+ ...row.record ? { attempts: row.record.attempts, acceptedBy: row.record.accepted.by, openQuestions: openQuestions(row.record),
80
+ acknowledgements: row.record.acknowledgements.map(({ by, note, findings }) => ({ by, note, findings })),
81
+ awaitingCi: pendingCi(row.record), ...row.record.grade ? { grade: { mode: row.record.grade.mode, passed: row.record.grade.passed,
82
+ criteria: row.record.grade.criteria, outOfScope: row.record.grade.outOfScope, ...row.record.grade.evaluator ? { evaluator: row.record.grade.evaluator } : {} } } : {} } : {},
83
+ ...row.order ? { waitingOn: row.order.depends_on.filter(dependency => !closed.has(dependency)) } : {},
84
+ ...row.record && !TERMINAL.has(row.record.state) ? { openDecisions: await unresolvedDecisions(row.record, composition) } : {},
85
+ ...row.order && !TERMINAL.has(row.record?.state ?? 'accepted') ? await relatedDecisions(context, row.order) : {},
86
+ ...row.problems.length ? { problems: row.problems } : {},
87
+ }))),
88
+ });
89
+ }
90
+ if (request.operation === 'work-accept') {
91
+ const row = await observe(request.workOrderId);
92
+ if (!row.order)
93
+ return refusal(operation, `workorders/${request.workOrderId}.md is not a work order: ${row.problems.join('; ')}`);
94
+ const order = row.order;
95
+ const shownOrder = [`Objective: ${order.objective}`, `Scope: ${order.scope.join(', ')}`, ...order.criteria.map(criterion => `${criterion.id}: ${criterion.text}${criterion.check ? ` [check: ${JSON.stringify(criterion.check)}]` : ''}`),
96
+ ...order.gates.length ? [`Gates: ${order.gates.join(', ')}`] : [], ...order.non_goals.map(goal => `Non-goal: ${goal}`)];
97
+ if (!row.record) {
98
+ if (await wip() >= composition.caps.wip)
99
+ return refusal(operation, `the work-in-progress cap is reached (${composition.caps.wip} accepted orders unfinished); close one with work signoff first`);
100
+ const sources = await composition.sourceDigests(order);
101
+ const base = await composition.heads();
102
+ const digest = workOrderDigest(order, sources, base);
103
+ return gated('accept', policy.gates.accept, [`Accept ${order.id} (digest ${digest})`, ...shownOrder], 'accept', async (by) => store.update(order.id, async (existing) => {
104
+ if (existing)
105
+ throw new RefusalError(`${order.id} was accepted while this prompt was open; rerun work status`);
106
+ const record = { format: 'cockpit-work-order/v1', id: order.id, state: 'accepted',
107
+ accepted: { at: now(), by, digest, order, sources, base }, amendments: [], acknowledgements: [], attempts: 0, notes: [] };
108
+ return { record, value: complete(operation, { id: order.id, state: 'accepted', by, digest, ...by === 'ci' ? { awaitingCi: ['accept'] } : {}, ...await relatedDecisions(context, order) }) };
109
+ }));
110
+ }
111
+ const record = row.record;
112
+ if (TERMINAL.has(record.state))
113
+ return refusal(operation, `${order.id} is closed (${record.state}); write a new order for further work`);
114
+ if (row.digest === record.accepted.digest)
115
+ return refusal(operation, `${order.id} is already accepted and unchanged since`);
116
+ const { removed: weakened, review } = contractAmendment(record.accepted.order, order);
117
+ // Removing a criterion or gate lowers the bar in every reading, so an agent never certifies it. A rewrite may
118
+ // only clarify: where a model grades, the next grade judges it; under checks nothing could, so a person does.
119
+ // So does the amendment after a grade that found the agent's earlier ones lowered the bar.
120
+ const unjudgeable = policy.grading === 'checks' ? review.filter(row => checksBindsField(row.field)) : [];
121
+ const failedReview = record.amendmentsFailedReview;
122
+ const decider = policy.gates.amend === 'agent' && (weakened.length || unjudgeable.length || failedReview) ? 'person' : policy.gates.amend;
123
+ const sources = await composition.sourceDigests(order);
124
+ const digest = workOrderDigest(order, sources, record.accepted.base);
125
+ return gated('amend', decider, [`Amend ${order.id} (digest ${record.accepted.digest} -> ${digest})`,
126
+ ...weakened.length ? [`This weakens the accepted contract: ${weakened.join('; ')}`] : [],
127
+ ...failedReview ? [`A grade found the agent's earlier amendments lower the bar: ${failedReview.note}`] : [],
128
+ ...review.map(row => `Changed ${row.field}: "${row.before}" -> "${row.after}"`), ...shownOrder], 'amend', async (by) => store.update(order.id, async (existing) => {
129
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
130
+ throw new RefusalError(`${order.id} changed while this prompt was open; rerun work accept`);
131
+ const next = { ...existing, state: 'accepted', attempts: 0, checks: undefined, grade: undefined,
132
+ ...by === 'agent' ? {} : { amendmentsFailedReview: undefined },
133
+ accepted: { at: now(), by, digest, order, sources, base: existing.accepted.base },
134
+ amendments: [...existing.amendments, { at: now(), by, from: existing.accepted.digest, to: digest, weakened: [...weakened], review: [...review] }] };
135
+ return { record: next, value: complete(operation, { id: order.id, state: 'accepted', by, digest, amended: true, weakened,
136
+ ...by === 'agent' && review.length && policy.grading !== 'checks' ? { judgedAtGrade: review.map(row => row.field) } : {}, ...await relatedDecisions(context, order) }) };
137
+ }));
138
+ }
139
+ if (request.operation === 'work-check') {
140
+ const { order, record } = await current(request.workOrderId);
141
+ const results = await runChecks(order);
142
+ if (!results.length)
143
+ return refusal(operation, `${order.id} declares no check on any criterion and no gates; add a check, or close it under [work] grading = "self"`);
144
+ const passed = results.every(row => row.passed);
145
+ await store.update(order.id, async (existing) => {
146
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
147
+ throw new RefusalError(`${order.id} changed while its checks ran; rerun work check`);
148
+ return { record: { ...existing, state: passed ? 'checked' : 'accepted', attempts: existing.attempts + (passed ? 0 : 1), grade: undefined,
149
+ checks: { at: now(), digest: record.accepted.digest, results: [...results] } }, value: undefined };
150
+ });
151
+ const value = { id: order.id, state: passed ? 'checked' : 'accepted', results };
152
+ return passed ? complete(operation, value) : unsatisfied(operation, value, `${results.filter(row => !row.passed).map(row => `${row.criterion}: ${row.outcome}`).join('; ')}`);
153
+ }
154
+ if (request.operation === 'work-note') {
155
+ if (request.kind === 'decision' && !composition.docs?.decisions)
156
+ return refusal(operation, DECISIONS_UNDECLARED);
157
+ await current(request.workOrderId).catch(async (error) => {
158
+ // A note on a stale or blocked order is how its cause gets recorded, so only a closed or unaccepted one refuses.
159
+ const record = await store.read(request.workOrderId);
160
+ if (!record || TERMINAL.has(record.state))
161
+ throw error;
162
+ });
163
+ const index = await store.update(request.workOrderId, async (existing) => {
164
+ if (!existing)
165
+ throw new RefusalError(`${request.workOrderId} is not accepted`);
166
+ return { record: { ...existing, notes: [...existing.notes, { at: now(), by: 'agent', kind: request.kind, text: request.text }] }, value: existing.notes.length + 1 };
167
+ });
168
+ return complete(operation, { id: request.workOrderId, note: index, ...request.kind === 'question' ? { question: index, state: 'blocked' } : {},
169
+ ...request.kind === 'decision' ? { decision: index, resolveWith: `npx --no cockpit work decide --config <your cockpit.toml> --work-order ${request.workOrderId} --decision ${index} --record` } : {} });
170
+ }
171
+ if (request.operation === 'work-decide')
172
+ return decideOrder(request, context);
173
+ if (request.operation === 'work-answer') {
174
+ const record = await store.read(request.workOrderId);
175
+ if (!record || TERMINAL.has(record.state))
176
+ return refusal(operation, `${request.workOrderId} has no open record to answer on`);
177
+ const question = record.notes[request.question - 1];
178
+ if (!question || question.kind !== 'question' || !openQuestions(record).includes(request.question))
179
+ return refusal(operation, `note ${request.question} on ${request.workOrderId} is not an open question`);
180
+ return gated('answer', policy.gates.answer, [`Open question ${request.question} on ${request.workOrderId}:`, ` ${question.text}`, 'Answer to record:', ` ${request.text}`], 'answer', async (by) => store.update(request.workOrderId, async (existing) => {
181
+ if (!existing || !openQuestions(existing).includes(request.question))
182
+ throw new RefusalError(`question ${request.question} was answered while this prompt was open`);
183
+ return { record: { ...existing, notes: [...existing.notes, { at: now(), by, kind: 'answer', text: request.text, answers: request.question }] },
184
+ value: complete(operation, { id: request.workOrderId, answered: request.question, by }) };
185
+ }));
186
+ }
187
+ if (request.operation === 'work-grade')
188
+ return gradeOrder(request, context);
189
+ if (request.operation === 'work-acknowledge') {
190
+ const { order, record } = await current(request.workOrderId);
191
+ const weak = await composition.testWeakening(record.accepted.base);
192
+ if (!weak.findings.length)
193
+ return refusal(operation, `${order.id} weakens no tests since its base, so there is nothing to acknowledge`);
194
+ const digest = testWeakeningDigest(weak.findings);
195
+ if (record.acknowledgements.some(row => row.digest === digest))
196
+ return refusal(operation, `this weakening of ${order.id} is already acknowledged; run work grade`);
197
+ const rows = weak.findings.map(describeWeakening);
198
+ return gated('acknowledge', policy.gates.acknowledge, [`Acknowledge that ${order.id} weakens tests since its base (test files from ${weak.patterns.source}):`,
199
+ ...rows.map(row => ` ${row}`), 'Reason given:', ` ${request.note}`], 'acknowledge', async (by) => store.update(order.id, async (existing) => {
200
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
201
+ throw new RefusalError(`${order.id} changed while this prompt was open; rerun work acknowledge`);
202
+ if (testWeakeningDigest((await composition.testWeakening(existing.accepted.base)).findings) !== digest) {
203
+ throw new RefusalError(`the test changes of ${order.id} changed while this prompt was open; rerun work acknowledge`);
204
+ }
205
+ return { record: { ...existing, acknowledgements: [...existing.acknowledgements, { at: now(), by, digest, findings: rows, note: request.note }] },
206
+ value: complete(operation, { id: order.id, acknowledged: rows, by, digest }) };
207
+ }));
208
+ }
209
+ if (request.operation === 'work-signoff')
210
+ return signoff(request, context);
211
+ if (request.operation === 'work-recover') {
212
+ // A pid probe sees only this machine's processes, so the command looks where it cannot first: the
213
+ // lock's host and the containers cockpit labels. Applied, it removes nothing it has not shown gone.
214
+ const recovered = await store.recover(request.workOrderId, request.apply, composition.containers);
215
+ if (!request.apply || recovered.lock === 'none' || recovered.lock === 'removed')
216
+ return complete(operation, recovered);
217
+ return refusal(operation, `the lock on ${request.workOrderId} was left in place: its holder ${recovered.lock === 'live' ? 'is still running' : 'cannot be shown gone'} `
218
+ + `(${recovered.checked?.join('; ')}); wait for it to finish, or stop it on the host that holds it, then rerun work recover --apply`);
219
+ }
220
+ if (request.operation === 'work-verify')
221
+ return verify(context);
222
+ return gradeCriteriaFile(request, context);
223
+ }
@@ -0,0 +1,101 @@
1
+ import { type EvaluatorTool } from '../schema.js';
2
+ import { type EffectivePolicy } from '../autonomy.js';
3
+ import { type ChangedFile, type GradingSubject, type Verdict } from '../grading/grade.js';
4
+ import { type BrowserCommandInput, type BrowserCommandKind } from '../browser/commands.js';
5
+ import { type RepositoryWeakening, type TestFilePatterns } from '../testing/weakening.js';
6
+ import { type GateDependencies, type GateRegistry, type GateRequest } from '../testing/gates.js';
7
+ import { parseCriteriaFile, type ParsedFile, type WorkOrder, type WorkOrderCheck } from './contract.js';
8
+ import { type CheckResult, type ContainerEvidence, type WorkStore } from './store.js';
9
+ type GradingResult = Readonly<{
10
+ criteria: readonly Verdict[];
11
+ evaluator: Readonly<{
12
+ tool: string;
13
+ fallbacks: readonly Readonly<{
14
+ tool: string;
15
+ code: string;
16
+ message: string;
17
+ }>[];
18
+ }>;
19
+ }> | Readonly<{
20
+ refusal: Readonly<{
21
+ code: string;
22
+ message: string;
23
+ fix: string;
24
+ }>;
25
+ }>;
26
+ export interface WorkApplicationComposition {
27
+ readonly policy: EffectivePolicy;
28
+ readonly caps: Readonly<{
29
+ wip: number;
30
+ attempts: number;
31
+ }>;
32
+ readonly env: Readonly<Record<string, string | undefined>>;
33
+ readonly store: WorkStore;
34
+ readonly recordedPolicy: () => Promise<EffectivePolicy | undefined>;
35
+ readonly orderIds: () => Promise<readonly string[]>;
36
+ readonly readOrder: (id: string) => Promise<ParsedFile<WorkOrder>>;
37
+ readonly readCriteriaFile: (path: string) => Promise<ReturnType<typeof parseCriteriaFile>>;
38
+ readonly sourceDigests: (order: Pick<WorkOrder, 'criteria'>) => Promise<Readonly<Record<string, string>>>;
39
+ readonly heads: () => Promise<Readonly<Record<string, string>>>;
40
+ readonly runCheck: (criterion: string, check: WorkOrderCheck) => Promise<CheckResult>;
41
+ readonly evaluate: (subject: GradingSubject) => Promise<GradingResult>;
42
+ /** Test weakening in each checkout since `base`, over the files this cockpit treats as tests. */
43
+ readonly testWeakening: (base: Readonly<Record<string, string>>) => Promise<TestWeakeningObservation>;
44
+ /** What changed in each checkout since `base` (committed, staged, unstaged and untracked, each with its diff), and its test weakening, from one listing of untracked files. */
45
+ readonly review: (base: Readonly<Record<string, string>>) => Promise<Readonly<{
46
+ changes: readonly ChangedFile[];
47
+ weakening: TestWeakeningObservation;
48
+ }>>;
49
+ readonly browser: (kind: BrowserCommandKind, role?: string, route?: string) => Promise<BrowserCommandInput>;
50
+ /** The [docs] table as declared; absent when the cockpit declares none. */
51
+ readonly docs?: Readonly<{
52
+ decisions?: string;
53
+ decisionsIndex?: string;
54
+ }>;
55
+ /** Each Markdown file in [docs] decisions, by reference, with its text; empty when the directory does not exist yet. */
56
+ readonly decisionRecords: () => Promise<ReadonlyMap<string, string>>;
57
+ /** A decision record's or the index's text, or undefined when the file does not exist. */
58
+ readonly readDocsFile: (reference: string) => Promise<string | undefined>;
59
+ /** Writes each record, a new one exclusively and an existing one replaced, then appends `indexLine` to [docs] decisions_index. */
60
+ readonly writeDecisionRecords: (writes: readonly Readonly<{
61
+ reference: string;
62
+ text: string;
63
+ create: boolean;
64
+ }>[], indexLine?: string) => Promise<void>;
65
+ readonly gate: (request: WorkApplicationGateRequest) => Promise<Readonly<{
66
+ registry: GateRegistry;
67
+ request: GateRequest;
68
+ dependencies: GateDependencies;
69
+ }>>;
70
+ /** The containers cockpit labels on this machine's local Docker daemon, where a lock's holder may run unseen by a pid probe. */
71
+ readonly containers: () => Promise<ContainerEvidence>;
72
+ }
73
+ type TestWeakeningObservation = Readonly<{
74
+ patterns: TestFilePatterns;
75
+ findings: readonly RepositoryWeakening[];
76
+ }>;
77
+ interface WorkApplicationGateRequest {
78
+ readonly operation: 'test-run';
79
+ readonly gate: string;
80
+ readonly mode: 'canonical' | 'full' | 'staged';
81
+ readonly select?: Readonly<{
82
+ kind: 'spec' | 'file' | 'test' | 'route';
83
+ value: string;
84
+ }>;
85
+ }
86
+ export declare function isWorkApplicationComposition(value: unknown): value is WorkApplicationComposition;
87
+ type ConfiguredWorkApplicationCompositionResult = Readonly<{
88
+ kind: 'complete';
89
+ composition: WorkApplicationComposition;
90
+ }> | Readonly<{
91
+ kind: 'refusal';
92
+ reason: string;
93
+ }>;
94
+ export declare function createConfiguredWorkApplicationComposition(input: Readonly<{
95
+ configPath: string;
96
+ env: Readonly<Record<string, string | undefined>>;
97
+ }>, options?: Readonly<{
98
+ /** Grade with only this tool, with no fallback: the person named the grader for this run. */
99
+ evaluator?: EvaluatorTool;
100
+ }>): Promise<ConfiguredWorkApplicationCompositionResult>;
101
+ export {};