@b00dea/cockpit 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -1
  4. package/bin/cockpit.mjs +33 -0
  5. package/cockpit.schema.json +2574 -0
  6. package/dist/autonomy.d.ts +44 -0
  7. package/dist/autonomy.js +156 -0
  8. package/dist/bounded-read.d.ts +14 -0
  9. package/dist/bounded-read.js +42 -0
  10. package/dist/browser/commands.d.ts +62 -0
  11. package/dist/browser/commands.js +349 -0
  12. package/dist/browser/package-paths.d.ts +1 -0
  13. package/dist/browser/package-paths.js +6 -0
  14. package/dist/browser/proxy.d.ts +22 -0
  15. package/dist/browser/proxy.js +37 -0
  16. package/dist/browser/runtime.d.ts +117 -0
  17. package/dist/browser/runtime.js +183 -0
  18. package/dist/browser/surfaces.d.ts +141 -0
  19. package/dist/browser/surfaces.js +556 -0
  20. package/dist/browser/ui-gate.d.ts +15 -0
  21. package/dist/browser/ui-gate.js +18 -0
  22. package/dist/cli-guard.d.ts +5 -0
  23. package/dist/cli-guard.js +71 -0
  24. package/dist/cli-main.d.ts +139 -0
  25. package/dist/cli-main.js +1405 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.js +16 -0
  28. package/dist/command-table.d.ts +113 -0
  29. package/dist/command-table.js +512 -0
  30. package/dist/config/load.d.ts +171 -0
  31. package/dist/config/load.js +345 -0
  32. package/dist/config/sibling-toml.d.ts +48 -0
  33. package/dist/config/sibling-toml.js +160 -0
  34. package/dist/config/surface-tools-toml.d.ts +9 -0
  35. package/dist/config/surface-tools-toml.js +37 -0
  36. package/dist/config/verified-write.d.ts +43 -0
  37. package/dist/config/verified-write.js +89 -0
  38. package/dist/diagnostics.d.ts +26 -0
  39. package/dist/diagnostics.js +335 -0
  40. package/dist/emit.d.ts +22 -0
  41. package/dist/emit.js +43 -0
  42. package/dist/entrypoint-kinds.d.ts +3 -0
  43. package/dist/entrypoint-kinds.js +11 -0
  44. package/dist/environment/application/destructive.d.ts +6 -0
  45. package/dist/environment/application/destructive.js +425 -0
  46. package/dist/environment/application/doctor.d.ts +89 -0
  47. package/dist/environment/application/doctor.js +235 -0
  48. package/dist/environment/application/guard.d.ts +43 -0
  49. package/dist/environment/application/guard.js +622 -0
  50. package/dist/environment/application/health.d.ts +134 -0
  51. package/dist/environment/application/health.js +379 -0
  52. package/dist/environment/application/lifecycle-context.d.ts +228 -0
  53. package/dist/environment/application/lifecycle-context.js +154 -0
  54. package/dist/environment/application/lifecycle-state.d.ts +25 -0
  55. package/dist/environment/application/lifecycle-state.js +201 -0
  56. package/dist/environment/application/lifecycle.d.ts +17 -0
  57. package/dist/environment/application/lifecycle.js +729 -0
  58. package/dist/environment/application/logs.d.ts +45 -0
  59. package/dist/environment/application/logs.js +127 -0
  60. package/dist/environment/application/setup.d.ts +129 -0
  61. package/dist/environment/application/setup.js +741 -0
  62. package/dist/environment/application/shared.d.ts +11 -0
  63. package/dist/environment/application/shared.js +61 -0
  64. package/dist/environment/claims.d.ts +389 -0
  65. package/dist/environment/claims.js +1709 -0
  66. package/dist/environment/codex-hook-trust.d.ts +11 -0
  67. package/dist/environment/codex-hook-trust.js +185 -0
  68. package/dist/environment/compose-environment.d.ts +1 -0
  69. package/dist/environment/compose-environment.js +6 -0
  70. package/dist/environment/compose-ownership.d.ts +21 -0
  71. package/dist/environment/compose-ownership.js +117 -0
  72. package/dist/environment/compose-topology.d.ts +117 -0
  73. package/dist/environment/compose-topology.js +413 -0
  74. package/dist/environment/contract.d.ts +489 -0
  75. package/dist/environment/contract.js +761 -0
  76. package/dist/environment/declaration.d.ts +14 -0
  77. package/dist/environment/declaration.js +39 -0
  78. package/dist/environment/destructive-composition.d.ts +29 -0
  79. package/dist/environment/destructive-composition.js +333 -0
  80. package/dist/environment/docker-daemon.d.ts +30 -0
  81. package/dist/environment/docker-daemon.js +78 -0
  82. package/dist/environment/docker-endpoint.d.ts +10 -0
  83. package/dist/environment/docker-endpoint.js +19 -0
  84. package/dist/environment/env-schema-observation.d.ts +46 -0
  85. package/dist/environment/env-schema-observation.js +241 -0
  86. package/dist/environment/evidence.d.ts +136 -0
  87. package/dist/environment/evidence.js +437 -0
  88. package/dist/environment/executable.d.ts +26 -0
  89. package/dist/environment/executable.js +167 -0
  90. package/dist/environment/generated-application/authorities.d.ts +5 -0
  91. package/dist/environment/generated-application/authorities.js +61 -0
  92. package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
  93. package/dist/environment/generated-application/bootstrap-skill.js +328 -0
  94. package/dist/environment/generated-application/check.d.ts +13 -0
  95. package/dist/environment/generated-application/check.js +189 -0
  96. package/dist/environment/generated-application/custom-surface.d.ts +26 -0
  97. package/dist/environment/generated-application/custom-surface.js +289 -0
  98. package/dist/environment/generated-application/prepare.d.ts +29 -0
  99. package/dist/environment/generated-application/prepare.js +276 -0
  100. package/dist/environment/generated-application/publication.d.ts +33 -0
  101. package/dist/environment/generated-application/publication.js +547 -0
  102. package/dist/environment/generated-application/shared.d.ts +484 -0
  103. package/dist/environment/generated-application/shared.js +349 -0
  104. package/dist/environment/generated-application/surface-observation.d.ts +4 -0
  105. package/dist/environment/generated-application/surface-observation.js +69 -0
  106. package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
  107. package/dist/environment/generated-application/surface-outputs.js +389 -0
  108. package/dist/environment/generated-application/surfaces.d.ts +3 -0
  109. package/dist/environment/generated-application/surfaces.js +699 -0
  110. package/dist/environment/generated-application/sync.d.ts +6 -0
  111. package/dist/environment/generated-application/sync.js +109 -0
  112. package/dist/environment/git-observation.d.ts +183 -0
  113. package/dist/environment/git-observation.js +544 -0
  114. package/dist/environment/health-composition.d.ts +11 -0
  115. package/dist/environment/health-composition.js +628 -0
  116. package/dist/environment/host-observation.d.ts +56 -0
  117. package/dist/environment/host-observation.js +52 -0
  118. package/dist/environment/identity.d.ts +48 -0
  119. package/dist/environment/identity.js +85 -0
  120. package/dist/environment/init-application.d.ts +308 -0
  121. package/dist/environment/init-application.js +697 -0
  122. package/dist/environment/install-policy-observation.d.ts +71 -0
  123. package/dist/environment/install-policy-observation.js +284 -0
  124. package/dist/environment/install-policy.d.ts +48 -0
  125. package/dist/environment/install-policy.js +96 -0
  126. package/dist/environment/instance.d.ts +33 -0
  127. package/dist/environment/instance.js +122 -0
  128. package/dist/environment/instruction-pointer.d.ts +19 -0
  129. package/dist/environment/instruction-pointer.js +144 -0
  130. package/dist/environment/lifecycle-composition.d.ts +29 -0
  131. package/dist/environment/lifecycle-composition.js +675 -0
  132. package/dist/environment/lifecycle-stage.d.ts +115 -0
  133. package/dist/environment/lifecycle-stage.js +398 -0
  134. package/dist/environment/lifecycle.d.ts +75 -0
  135. package/dist/environment/lifecycle.js +354 -0
  136. package/dist/environment/logs.d.ts +132 -0
  137. package/dist/environment/logs.js +519 -0
  138. package/dist/environment/observations.d.ts +46 -0
  139. package/dist/environment/observations.js +344 -0
  140. package/dist/environment/operator-application.d.ts +50 -0
  141. package/dist/environment/operator-application.js +155 -0
  142. package/dist/environment/ports.d.ts +182 -0
  143. package/dist/environment/ports.js +1205 -0
  144. package/dist/environment/posture-observation.d.ts +68 -0
  145. package/dist/environment/posture-observation.js +253 -0
  146. package/dist/environment/read-composition.d.ts +27 -0
  147. package/dist/environment/read-composition.js +352 -0
  148. package/dist/environment/readiness-composition.d.ts +38 -0
  149. package/dist/environment/readiness-composition.js +130 -0
  150. package/dist/environment/reclaim.d.ts +333 -0
  151. package/dist/environment/reclaim.js +1835 -0
  152. package/dist/environment/repository-identity.d.ts +13 -0
  153. package/dist/environment/repository-identity.js +108 -0
  154. package/dist/environment/runtime-measurement-record.d.ts +38 -0
  155. package/dist/environment/runtime-measurement-record.js +111 -0
  156. package/dist/environment/runtime-measurement.d.ts +27 -0
  157. package/dist/environment/runtime-measurement.js +224 -0
  158. package/dist/environment/runtime-probe-residue.d.ts +43 -0
  159. package/dist/environment/runtime-probe-residue.js +148 -0
  160. package/dist/environment/runtime-probe.d.ts +72 -0
  161. package/dist/environment/runtime-probe.js +725 -0
  162. package/dist/environment/runtime.d.ts +505 -0
  163. package/dist/environment/runtime.js +2335 -0
  164. package/dist/environment/seed.d.ts +16 -0
  165. package/dist/environment/seed.js +43 -0
  166. package/dist/environment/selftest-application.d.ts +86 -0
  167. package/dist/environment/selftest-application.js +713 -0
  168. package/dist/environment/sensitive.d.ts +3 -0
  169. package/dist/environment/sensitive.js +48 -0
  170. package/dist/environment/setup-observation.d.ts +62 -0
  171. package/dist/environment/setup-observation.js +1473 -0
  172. package/dist/environment/setup.d.ts +43 -0
  173. package/dist/environment/setup.js +176 -0
  174. package/dist/environment/sibling-instructions.d.ts +33 -0
  175. package/dist/environment/sibling-instructions.js +224 -0
  176. package/dist/environment/sibling-registration.d.ts +45 -0
  177. package/dist/environment/sibling-registration.js +538 -0
  178. package/dist/environment/state.d.ts +371 -0
  179. package/dist/environment/state.js +2013 -0
  180. package/dist/environment/templates.d.ts +140 -0
  181. package/dist/environment/templates.js +685 -0
  182. package/dist/environment/tool-registration.d.ts +9 -0
  183. package/dist/environment/tool-registration.js +96 -0
  184. package/dist/environment/upgrade-application.d.ts +19 -0
  185. package/dist/environment/upgrade-application.js +175 -0
  186. package/dist/environment-exec/execute.d.ts +102 -0
  187. package/dist/environment-exec/execute.js +1175 -0
  188. package/dist/environment-exec/libexec-program.d.ts +6 -0
  189. package/dist/environment-exec/libexec-program.js +22 -0
  190. package/dist/environment-exec/verified-digest.d.ts +4 -0
  191. package/dist/environment-exec/verified-digest.js +16 -0
  192. package/dist/exact-keys.d.ts +7 -0
  193. package/dist/exact-keys.js +15 -0
  194. package/dist/fix-catalogue.d.ts +32 -0
  195. package/dist/fix-catalogue.js +60 -0
  196. package/dist/freshness.d.ts +29 -0
  197. package/dist/freshness.js +75 -0
  198. package/dist/generated-environment/adopt.d.ts +16 -0
  199. package/dist/generated-environment/adopt.js +275 -0
  200. package/dist/generated-environment/command.d.ts +47 -0
  201. package/dist/generated-environment/command.js +75 -0
  202. package/dist/generated-environment/transaction.d.ts +186 -0
  203. package/dist/generated-environment/transaction.js +1770 -0
  204. package/dist/grading/evaluate.d.ts +71 -0
  205. package/dist/grading/evaluate.js +403 -0
  206. package/dist/grading/grade.d.ts +33 -0
  207. package/dist/grading/grade.js +57 -0
  208. package/dist/guards/evaluator.d.ts +113 -0
  209. package/dist/guards/evaluator.js +1226 -0
  210. package/dist/guards/harness-adapter.d.ts +17 -0
  211. package/dist/guards/harness-adapter.js +57 -0
  212. package/dist/guards/host.d.ts +8 -0
  213. package/dist/guards/host.js +10 -0
  214. package/dist/guards/invocation-parser.d.ts +66 -0
  215. package/dist/guards/invocation-parser.js +595 -0
  216. package/dist/guards/local-identity.d.ts +40 -0
  217. package/dist/guards/local-identity.js +123 -0
  218. package/dist/guards/policy-pin.d.ts +13 -0
  219. package/dist/guards/policy-pin.js +175 -0
  220. package/dist/guards/protected-locations.d.ts +62 -0
  221. package/dist/guards/protected-locations.js +461 -0
  222. package/dist/guards/read-only.d.ts +37 -0
  223. package/dist/guards/read-only.js +315 -0
  224. package/dist/init/command.d.ts +40 -0
  225. package/dist/init/command.js +310 -0
  226. package/dist/init/derive.d.ts +51 -0
  227. package/dist/init/derive.js +715 -0
  228. package/dist/init/draft.d.ts +138 -0
  229. package/dist/init/draft.js +1205 -0
  230. package/dist/init/render.d.ts +35 -0
  231. package/dist/init/render.js +82 -0
  232. package/dist/init/scaffold.d.ts +53 -0
  233. package/dist/init/scaffold.js +121 -0
  234. package/dist/invocation.d.ts +13 -0
  235. package/dist/invocation.js +20 -0
  236. package/dist/package-metadata.d.ts +9 -0
  237. package/dist/package-metadata.js +19 -0
  238. package/dist/readiness.d.ts +53 -0
  239. package/dist/readiness.js +88 -0
  240. package/dist/references/named.d.ts +24 -0
  241. package/dist/references/named.js +27 -0
  242. package/dist/refusal.d.ts +15 -0
  243. package/dist/refusal.js +7 -0
  244. package/dist/registry-derivation.d.ts +31 -0
  245. package/dist/registry-derivation.js +32 -0
  246. package/dist/registry-resolve/command.d.ts +47 -0
  247. package/dist/registry-resolve/command.js +226 -0
  248. package/dist/registry-resolve/registry.d.ts +68 -0
  249. package/dist/registry-resolve/registry.js +292 -0
  250. package/dist/registry-resolve/render-shell.d.ts +18 -0
  251. package/dist/registry-resolve/render-shell.js +36 -0
  252. package/dist/repair.d.ts +29 -0
  253. package/dist/repair.js +8 -0
  254. package/dist/schema-json.d.ts +1 -0
  255. package/dist/schema-json.js +177 -0
  256. package/dist/schema.d.ts +1322 -0
  257. package/dist/schema.js +934 -0
  258. package/dist/setup/command.d.ts +4 -0
  259. package/dist/setup/command.js +4 -0
  260. package/dist/sha256.d.ts +1 -0
  261. package/dist/sha256.js +4 -0
  262. package/dist/surfaces/adoption.d.ts +41 -0
  263. package/dist/surfaces/adoption.js +217 -0
  264. package/dist/surfaces/consumer-instructions.d.ts +20 -0
  265. package/dist/surfaces/consumer-instructions.js +43 -0
  266. package/dist/surfaces/contract.d.ts +42 -0
  267. package/dist/surfaces/contract.js +169 -0
  268. package/dist/surfaces/core-declarations.d.ts +24 -0
  269. package/dist/surfaces/core-declarations.js +79 -0
  270. package/dist/surfaces/development-markers.d.ts +1 -0
  271. package/dist/surfaces/development-markers.js +18 -0
  272. package/dist/surfaces/generator-adapter.d.ts +71 -0
  273. package/dist/surfaces/generator-adapter.js +809 -0
  274. package/dist/surfaces/harness-registry.d.ts +180 -0
  275. package/dist/surfaces/harness-registry.js +117 -0
  276. package/dist/surfaces/mcp.d.ts +95 -0
  277. package/dist/surfaces/mcp.js +186 -0
  278. package/dist/surfaces/merge.d.ts +30 -0
  279. package/dist/surfaces/merge.js +105 -0
  280. package/dist/surfaces/ownership.d.ts +190 -0
  281. package/dist/surfaces/ownership.js +795 -0
  282. package/dist/surfaces/projection.d.ts +136 -0
  283. package/dist/surfaces/projection.js +681 -0
  284. package/dist/surfaces/render.d.ts +118 -0
  285. package/dist/surfaces/render.js +633 -0
  286. package/dist/surfaces/restrictions.d.ts +130 -0
  287. package/dist/surfaces/restrictions.js +447 -0
  288. package/dist/surfaces/selftest.d.ts +162 -0
  289. package/dist/surfaces/selftest.js +393 -0
  290. package/dist/surfaces/skill-contract.d.ts +85 -0
  291. package/dist/surfaces/skill-contract.js +449 -0
  292. package/dist/surfaces/sync.d.ts +57 -0
  293. package/dist/surfaces/sync.js +164 -0
  294. package/dist/team-metadata.d.ts +41 -0
  295. package/dist/team-metadata.js +512 -0
  296. package/dist/testing/gates.d.ts +91 -0
  297. package/dist/testing/gates.js +182 -0
  298. package/dist/testing/hooks.d.ts +59 -0
  299. package/dist/testing/hooks.js +139 -0
  300. package/dist/testing/verdict.d.ts +29 -0
  301. package/dist/testing/verdict.js +62 -0
  302. package/dist/work-orders/application.d.ts +99 -0
  303. package/dist/work-orders/application.js +383 -0
  304. package/dist/work-orders/composition.d.ts +124 -0
  305. package/dist/work-orders/composition.js +1010 -0
  306. package/dist/work-orders/contract.d.ts +61 -0
  307. package/dist/work-orders/contract.js +122 -0
  308. package/dist/work-orders/store.d.ts +158 -0
  309. package/dist/work-orders/store.js +197 -0
  310. package/docs/README.md +65 -0
  311. package/docs/architecture.md +136 -0
  312. package/docs/capability-matrix.md +50 -0
  313. package/docs/cli-reference.md +599 -0
  314. package/docs/command-list.md +51 -0
  315. package/docs/compose-patterns.md +99 -0
  316. package/docs/concepts.md +171 -0
  317. package/docs/configuration-essentials.md +172 -0
  318. package/docs/configuration-reference.md +621 -0
  319. package/docs/environment.md +760 -0
  320. package/docs/extending.md +43 -0
  321. package/docs/faq.md +111 -0
  322. package/docs/getting-started.md +608 -0
  323. package/docs/harness-surfaces.md +433 -0
  324. package/docs/joining-a-team.md +76 -0
  325. package/docs/release-policy.md +49 -0
  326. package/docs/safety-and-autonomy.md +419 -0
  327. package/docs/troubleshooting.md +361 -0
  328. package/docs/tutorial.md +170 -0
  329. package/docs/work-and-browser.md +254 -0
  330. package/libexec/browser-operation.cjs +186 -0
  331. package/libexec/fence-marker.cjs +101 -0
  332. package/libexec/fence-marker.d.cts +24 -0
  333. package/libexec/fenced-group-runner.cjs +77 -0
  334. package/libexec/fenced-supervisor.cjs +137 -0
  335. package/libexec/generated-filesystem.cjs +731 -0
  336. package/libexec/install-filesystem.cjs +225 -0
  337. package/libexec/rulesync-generator.NOTICE.md +1112 -0
  338. package/libexec/rulesync-generator.cjs +743 -0
  339. package/libexec/surface-generator-worker.cjs +266 -0
  340. package/libexec/surface-output-reader.cjs +99 -0
  341. package/libexec/surface-scratch-cleanup.cjs +14 -0
  342. package/libexec/validate-harness-evidence.d.mts +183 -0
  343. package/libexec/validate-harness-evidence.mjs +1312 -0
  344. package/package.json +76 -1
  345. package/surfaces/README.md +33 -0
  346. package/surfaces/agents/evaluator.agent.md +76 -0
  347. package/surfaces/guards/bash-guard.sh +48 -0
  348. package/surfaces/guards/claude.adapter.json +25 -0
  349. package/surfaces/guards/codex.adapter.json +24 -0
  350. package/surfaces/guards/opencode.adapter.json +25 -0
  351. package/surfaces/references/browser.md +63 -0
  352. package/surfaces/references/test-data-and-personas.md +84 -0
  353. package/surfaces/references/testing-tiers.md +11 -0
  354. package/surfaces/skills/cockpit-browser.skill.md +54 -0
  355. package/surfaces/skills/cockpit-explain.skill.md +32 -0
  356. package/surfaces/skills/cockpit-fix.skill.md +37 -0
  357. package/surfaces/skills/cockpit-setup.skill.md +39 -0
  358. package/surfaces/skills/cockpit-work.skill.md +34 -0
  359. package/surfaces/skills/collection.json +5 -0
  360. package/surfaces/skills/procedures/explain.md +61 -0
  361. package/surfaces/skills/procedures/fix.md +81 -0
  362. package/surfaces/skills/procedures/setup.md +379 -0
  363. package/surfaces/skills/procedures/work.md +105 -0
@@ -0,0 +1,383 @@
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.
6
+ import { createHash } from 'node:crypto';
7
+ import { posix } from 'node:path';
8
+ import { z } from 'zod';
9
+ import { decide, policyWeakening, runningInCi } from '../autonomy.js';
10
+ import { executeBrowserCommand } from '../browser/commands.js';
11
+ import { evaluateUiGate } from '../browser/ui-gate.js';
12
+ import { errorCause, RefusalError } from '../environment/contract.js';
13
+ import { runGate } from '../testing/gates.js';
14
+ import { criteriaWeakening, scopeMatches, WORK_ORDER_ID, workOrderDigest } from './contract.js';
15
+ import { isWorkApplicationComposition } from './composition.js';
16
+ const id = z.string().regex(WORK_ORDER_ID);
17
+ const note = z.string().trim().min(1).max(4_096).regex(/^[^\x00-\x08\x0b-\x1f\x7f]*$/);
18
+ const relativeFile = z.string().max(4_096).regex(/^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._@+\-/]+$/);
19
+ const Request = z.discriminatedUnion('operation', [
20
+ z.object({ operation: z.literal('work-status'), workOrderId: id.optional() }).strict(),
21
+ z.object({ operation: z.literal('work-accept'), workOrderId: id }).strict(),
22
+ z.object({ operation: z.literal('work-check'), workOrderId: id }).strict(),
23
+ z.object({ operation: z.literal('work-note'), workOrderId: id, text: note, question: z.boolean() }).strict(),
24
+ z.object({ operation: z.literal('work-answer'), workOrderId: id, question: z.number().int().min(1), text: note }).strict(),
25
+ z.object({ operation: z.literal('work-grade'), workOrderId: id, verdict: z.enum(['met', 'unmet']).optional(), note: note.optional() }).strict(),
26
+ z.object({ operation: z.literal('work-signoff'), workOrderId: id, close: z.enum(['done', 'partial', 'abandoned']), note: note.optional() }).strict(),
27
+ z.object({ operation: z.literal('work-recover'), workOrderId: id, apply: z.boolean() }).strict(),
28
+ z.object({ operation: z.literal('work-verify') }).strict(),
29
+ z.object({ operation: z.literal('grade-criteria'), criteriaFile: relativeFile }).strict(),
30
+ z.object({ operation: z.enum(['browser-drive', 'browser-screenshot']), role: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).optional() }).strict(),
31
+ z.object({ operation: z.literal('test-run'), gate: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/), mode: z.enum(['canonical', 'full', 'staged']),
32
+ select: z.object({ kind: z.enum(['spec', 'file', 'test', 'route']), value: z.string().min(1).refine(value => Buffer.byteLength(value) <= 4_096 && !/[\x00-\x1f\x7f]/.test(value)) }).strict().optional() }).strict(),
33
+ ]);
34
+ export function captureWorkApplicationRequest(value) {
35
+ const parsed = Request.safeParse(value);
36
+ return parsed.success ? parsed.data : undefined;
37
+ }
38
+ const refusal = (operation, reason) => ({ kind: 'refusal', operation, reason });
39
+ const complete = (operation, value) => ({ kind: 'complete', operation, value });
40
+ const unsatisfied = (operation, value, reason) => ({ kind: 'unsatisfied', operation, value, reason });
41
+ const now = () => new Date().toISOString();
42
+ // Agent-written text is shown to a person with its control and bidirectional-override characters
43
+ // replaced, so it can neither drive their terminal nor read in an order other than the one recorded.
44
+ const shown = (value) => value.replace(/[\x00-\x09\x0b-\x1f\x7f-\x9f‪-‮⁦-⁩]/g, '�');
45
+ const TERMINAL = new Set(['done', 'partial', 'abandoned']);
46
+ const CLOSURE = Object.freeze({ independent: 'graded', checks: 'checks-passed', self: 'self-reported' });
47
+ function displayState(row, attemptCap) {
48
+ if (!row.record)
49
+ return row.order ? 'draft' : 'invalid';
50
+ if (TERMINAL.has(row.record.state))
51
+ return row.record.state;
52
+ if (!row.order || row.digest !== row.record.accepted.digest)
53
+ return 'stale';
54
+ if (openQuestions(row.record).length || row.record.attempts >= attemptCap)
55
+ return 'blocked';
56
+ return row.record.state;
57
+ }
58
+ function openQuestions(record) {
59
+ return record.notes.flatMap((row, index) => row.kind === 'question' && !record.notes.some(answer => answer.answers === index + 1) ? [index + 1] : []);
60
+ }
61
+ const pendingCi = (record) => [
62
+ ...record.accepted.by === 'ci' ? ['accept'] : [], ...record.amendments.filter(row => row.by === 'ci').map(() => 'amend'),
63
+ ...record.notes.filter(row => row.kind === 'answer' && row.by === 'ci').map(() => 'answer'), ...record.closure?.by === 'ci' ? ['signoff'] : [],
64
+ ];
65
+ export async function runWorkApplication(request, composition) {
66
+ const captured = captureWorkApplicationRequest(request);
67
+ if (!captured)
68
+ return refusal('invalid', 'Work application requests require a closed selector-only shape');
69
+ if (!isWorkApplicationComposition(composition))
70
+ return refusal('invalid', 'An issued work application composition is required');
71
+ const operation = captured.operation;
72
+ try {
73
+ if (captured.operation === 'browser-drive' || captured.operation === 'browser-screenshot') {
74
+ const kind = captured.operation === 'browser-screenshot' ? 'screenshot' : 'drive';
75
+ const result = await executeBrowserCommand(await composition.browser(kind, captured.role));
76
+ if (result.kind === 'refusal')
77
+ return refusal(operation, result.reason);
78
+ if (result.kind === 'complete') {
79
+ const gate = evaluateUiGate({ activating: kind, receipts: [result.receipt] });
80
+ if (gate.kind === 'refusal')
81
+ return refusal(operation, gate.reason);
82
+ }
83
+ return complete(operation, result);
84
+ }
85
+ if (captured.operation === 'test-run') {
86
+ const prepared = await composition.gate(captured);
87
+ const run = await runGate(prepared.registry, prepared.request, prepared.dependencies);
88
+ return run.satisfiesGate ? complete(operation, run) : unsatisfied(operation, run, `${run.verdict}: ${run.reasons.join('; ') || 'declared gate was not satisfied'}`);
89
+ }
90
+ // A policy weakened in cockpit.toml since the last sync is not in force until a person accepts it there.
91
+ const recorded = await composition.recordedPolicy();
92
+ const weakened = recorded ? policyWeakening(recorded, composition.policy) : [];
93
+ if (weakened.length) {
94
+ return refusal(operation, `cockpit.toml weakens ${weakened.map(row => `${row.field} from ${row.from} to ${row.to}`).join(', ')} since the last sync; `
95
+ + 'a person runs npx --no cockpit sync --config <your cockpit.toml> --interactive and accepts the weakening, or the change is reverted');
96
+ }
97
+ return await work(captured, composition);
98
+ }
99
+ catch (error) {
100
+ return refusal(operation, errorCause(error));
101
+ }
102
+ }
103
+ async function work(request, composition) {
104
+ const { policy, store } = composition;
105
+ const operation = request.operation;
106
+ const observe = async (orderId) => {
107
+ const [parsed, record] = await Promise.all([composition.readOrder(orderId), store.read(orderId)]);
108
+ if (!parsed.ok)
109
+ return { id: orderId, problems: parsed.problems, ...(record ? { record } : {}) };
110
+ // A source that cannot be read leaves this order without a digest, and so stale, not the whole listing refused.
111
+ let sources;
112
+ try {
113
+ sources = await composition.sourceDigests(parsed.value);
114
+ }
115
+ catch (error) {
116
+ if (!(error instanceof RefusalError))
117
+ throw error;
118
+ return { id: orderId, order: parsed.value, problems: [`a cited source cannot be read: ${error.message}`], ...(record ? { record } : {}) };
119
+ }
120
+ const base = record?.accepted.base ?? await composition.heads();
121
+ return { id: orderId, order: parsed.value, problems: [], ...(record ? { record } : {}), digest: workOrderDigest(parsed.value, sources, base) };
122
+ };
123
+ const wip = async () => (await store.list()).filter(record => !TERMINAL.has(record.state)).length;
124
+ // Who passed a gate: the agent; a person, after typing the digest of exactly what they were shown;
125
+ // or CI, which locally records the step as awaiting the `work verify` job and passes it there.
126
+ const gated = async (gate, decider, lines, verb, apply) => {
127
+ const decision = decide(decider, composition.env);
128
+ if (decision === 'proceed')
129
+ return apply(decider === 'ci' ? 'ci' : 'agent');
130
+ if (decision === 'ci-only')
131
+ return apply('ci');
132
+ const digest = createHash('sha256').update(JSON.stringify({ gate, lines })).digest('hex');
133
+ let used = false;
134
+ return { kind: 'human-needed', operation, continuation: { digest,
135
+ prompt: [...lines.map(shown), `[work.gates] ${gate} = person. To ${verb} exactly this, type ${verb} ${digest}`].join('\n'),
136
+ decide: async (answer) => {
137
+ if (used)
138
+ return refusal(operation, `this prompt was already answered; rerun work ${operation.slice('work-'.length)}`);
139
+ used = true;
140
+ return answer.trim() === `${verb} ${digest}` ? apply('person') : refusal(operation, `Nothing was recorded: the person did not type ${verb} ${digest}`);
141
+ } } };
142
+ };
143
+ const current = async (orderId) => {
144
+ const row = await observe(orderId);
145
+ if (!row.record)
146
+ throw new RefusalError(`${orderId} is not accepted yet; run npx --no cockpit work accept --config <your cockpit.toml> --work-order ${orderId}`);
147
+ const state = displayState(row, composition.caps.attempts);
148
+ if (TERMINAL.has(row.record.state))
149
+ throw new RefusalError(`${orderId} is closed (${row.record.state})`);
150
+ if (state === 'stale')
151
+ throw new RefusalError(`${orderId} changed since it was accepted${row.problems.length ? ` and does not parse: ${row.problems.join('; ')}` : ''}; `
152
+ + `run npx --no cockpit work accept --config <your cockpit.toml> --work-order ${orderId} to accept the change`);
153
+ if (state === 'blocked')
154
+ throw new RefusalError(openQuestions(row.record).length
155
+ ? `${orderId} has open question ${openQuestions(row.record).join(', ')}; it is answered with work answer`
156
+ : `${orderId} reached the attempt cap (${composition.caps.attempts}); record the root cause with work note, then a person closes it with work signoff --partial or --abandon, or amends the contract`);
157
+ return { row, record: row.record, order: row.order };
158
+ };
159
+ const runChecks = async (order) => {
160
+ const results = [];
161
+ // Checks share the product's state, so they run in the order the contract lists them.
162
+ for (const criterion of order.criteria)
163
+ if (criterion.check)
164
+ results.push(await composition.runCheck(criterion.id, criterion.check));
165
+ for (const gate of order.gates)
166
+ results.push(await composition.runCheck(`gate:${gate}`, { gate }));
167
+ return results;
168
+ };
169
+ if (request.operation === 'work-status') {
170
+ const ids = request.workOrderId ? [request.workOrderId] : [...new Set([...await composition.orderIds(), ...(await store.list()).map(row => row.id)])].sort();
171
+ const rows = await Promise.all(ids.map(observe));
172
+ const closed = new Set(rows.filter(row => row.record?.state === 'done').map(row => row.id));
173
+ return complete(operation, {
174
+ grading: policy.grading, gates: policy.gates, wip: { open: await wip(), cap: composition.caps.wip },
175
+ orders: rows.map(row => ({
176
+ id: row.id, state: displayState(row, composition.caps.attempts),
177
+ ...row.record?.closure ? { closedAs: row.record.closure.how, closedBy: row.record.closure.by } : {},
178
+ ...row.record ? { attempts: row.record.attempts, acceptedBy: row.record.accepted.by, openQuestions: openQuestions(row.record),
179
+ awaitingCi: pendingCi(row.record), ...row.record.grade ? { grade: { mode: row.record.grade.mode, passed: row.record.grade.passed,
180
+ criteria: row.record.grade.criteria, outOfScope: row.record.grade.outOfScope, ...row.record.grade.evaluator ? { evaluator: row.record.grade.evaluator } : {} } } : {} } : {},
181
+ ...row.order ? { waitingOn: row.order.depends_on.filter(dependency => !closed.has(dependency)) } : {},
182
+ ...row.problems.length ? { problems: row.problems } : {},
183
+ })),
184
+ });
185
+ }
186
+ if (request.operation === 'work-accept') {
187
+ const row = await observe(request.workOrderId);
188
+ if (!row.order)
189
+ return refusal(operation, `workorders/${request.workOrderId}.md is not a work order: ${row.problems.join('; ')}`);
190
+ const order = row.order;
191
+ const shownOrder = [`Objective: ${order.objective}`, `Scope: ${order.scope.join(', ')}`, ...order.criteria.map(criterion => `${criterion.id}: ${criterion.text}${criterion.check ? ` [check: ${JSON.stringify(criterion.check)}]` : ''}`),
192
+ ...order.gates.length ? [`Gates: ${order.gates.join(', ')}`] : [], ...order.non_goals.map(goal => `Non-goal: ${goal}`)];
193
+ if (!row.record) {
194
+ if (await wip() >= composition.caps.wip)
195
+ return refusal(operation, `the work-in-progress cap is reached (${composition.caps.wip} accepted orders unfinished); close one with work signoff first`);
196
+ const sources = await composition.sourceDigests(order);
197
+ const base = await composition.heads();
198
+ const digest = workOrderDigest(order, sources, base);
199
+ return gated('accept', policy.gates.accept, [`Accept ${order.id} (digest ${digest})`, ...shownOrder], 'accept', async (by) => store.update(order.id, async (existing) => {
200
+ if (existing)
201
+ throw new RefusalError(`${order.id} was accepted while this prompt was open; rerun work status`);
202
+ const record = { format: 'cockpit-work-order/v1', id: order.id, state: 'accepted',
203
+ accepted: { at: now(), by, digest, order, sources, base }, amendments: [], attempts: 0, notes: [] };
204
+ return { record, value: complete(operation, { id: order.id, state: 'accepted', by, digest, ...by === 'ci' ? { awaitingCi: ['accept'] } : {} }) };
205
+ }));
206
+ }
207
+ const record = row.record;
208
+ if (TERMINAL.has(record.state))
209
+ return refusal(operation, `${order.id} is closed (${record.state}); write a new order for further work`);
210
+ if (row.digest === record.accepted.digest)
211
+ return refusal(operation, `${order.id} is already accepted and unchanged since`);
212
+ const weakened = criteriaWeakening(record.accepted.order, order);
213
+ // Removing or rewriting an accepted criterion moves the bar the work is held to, which an agent never certifies.
214
+ const decider = weakened.length && policy.gates.amend === 'agent' ? 'person' : policy.gates.amend;
215
+ const sources = await composition.sourceDigests(order);
216
+ const digest = workOrderDigest(order, sources, record.accepted.base);
217
+ return gated('amend', decider, [`Amend ${order.id} (digest ${record.accepted.digest} -> ${digest})`,
218
+ ...weakened.length ? [`This weakens the accepted contract: ${weakened.join('; ')}`] : [], ...shownOrder], 'amend', async (by) => store.update(order.id, async (existing) => {
219
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
220
+ throw new RefusalError(`${order.id} changed while this prompt was open; rerun work accept`);
221
+ const next = { ...existing, state: 'accepted', attempts: 0, checks: undefined, grade: undefined,
222
+ accepted: { at: now(), by, digest, order, sources, base: existing.accepted.base },
223
+ amendments: [...existing.amendments, { at: now(), by, from: existing.accepted.digest, to: digest, weakened: [...weakened] }] };
224
+ return { record: next, value: complete(operation, { id: order.id, state: 'accepted', by, digest, amended: true, weakened }) };
225
+ }));
226
+ }
227
+ if (request.operation === 'work-check') {
228
+ const { order, record } = await current(request.workOrderId);
229
+ const results = await runChecks(order);
230
+ if (!results.length)
231
+ return refusal(operation, `${order.id} declares no check on any criterion and no gates; add a check, or close it under [work] grading = "self"`);
232
+ const passed = results.every(row => row.passed);
233
+ await store.update(order.id, async (existing) => {
234
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
235
+ throw new RefusalError(`${order.id} changed while its checks ran; rerun work check`);
236
+ return { record: { ...existing, state: passed ? 'checked' : 'accepted', attempts: existing.attempts + (passed ? 0 : 1), grade: undefined,
237
+ checks: { at: now(), digest: record.accepted.digest, results: [...results] } }, value: undefined };
238
+ });
239
+ const value = { id: order.id, state: passed ? 'checked' : 'accepted', results };
240
+ return passed ? complete(operation, value) : unsatisfied(operation, value, `${results.filter(row => !row.passed).map(row => `${row.criterion}: ${row.outcome}`).join('; ')}`);
241
+ }
242
+ if (request.operation === 'work-note') {
243
+ await current(request.workOrderId).catch(async (error) => {
244
+ // A note on a stale or blocked order is how its cause gets recorded, so only a closed or unaccepted one refuses.
245
+ const record = await store.read(request.workOrderId);
246
+ if (!record || TERMINAL.has(record.state))
247
+ throw error;
248
+ });
249
+ const index = await store.update(request.workOrderId, async (existing) => {
250
+ if (!existing)
251
+ throw new RefusalError(`${request.workOrderId} is not accepted`);
252
+ return { record: { ...existing, notes: [...existing.notes, { at: now(), by: 'agent', kind: request.question ? 'question' : 'note', text: request.text }] }, value: existing.notes.length + 1 };
253
+ });
254
+ return complete(operation, { id: request.workOrderId, note: index, ...request.question ? { question: index, state: 'blocked' } : {} });
255
+ }
256
+ if (request.operation === 'work-answer') {
257
+ const record = await store.read(request.workOrderId);
258
+ if (!record || TERMINAL.has(record.state))
259
+ return refusal(operation, `${request.workOrderId} has no open record to answer on`);
260
+ const question = record.notes[request.question - 1];
261
+ if (!question || question.kind !== 'question' || !openQuestions(record).includes(request.question))
262
+ return refusal(operation, `note ${request.question} on ${request.workOrderId} is not an open question`);
263
+ 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) => {
264
+ if (!existing || !openQuestions(existing).includes(request.question))
265
+ throw new RefusalError(`question ${request.question} was answered while this prompt was open`);
266
+ return { record: { ...existing, notes: [...existing.notes, { at: now(), by, kind: 'answer', text: request.text, answers: request.question }] },
267
+ value: complete(operation, { id: request.workOrderId, answered: request.question, by }) };
268
+ }));
269
+ }
270
+ if (request.operation === 'work-grade') {
271
+ const { order, record } = await current(request.workOrderId);
272
+ if (policy.grading === 'self' ? !request.verdict || !request.note : request.verdict !== undefined || request.note !== undefined) {
273
+ return refusal(operation, policy.grading === 'self' ? '[work] grading = "self": pass --verdict met|unmet and --note <what you checked>'
274
+ : `--verdict and --note apply only under [work] grading = "self"; this cockpit grades with ${policy.grading}`);
275
+ }
276
+ let verdicts;
277
+ let evaluator;
278
+ const unchecked = order.criteria.filter(criterion => !criterion.check).map(criterion => criterion.id);
279
+ if (policy.grading === 'checks' && unchecked.length)
280
+ return refusal(operation, `[work] grading = "checks" closes an order only when cockpit ran a check for every criterion; ${unchecked.join(', ')} declare none`);
281
+ // Checks and scope bind in every mode, so an agent's own `met` covers neither a failed check nor a
282
+ // change outside scope. Changes are read after the checks, so files a check writes count too.
283
+ const checks = await runChecks(order);
284
+ const changes = await composition.changes(record.accepted.base);
285
+ const outOfScope = changes.map(change => change.path).filter(path => !scopeMatches(order.scope, path));
286
+ if (policy.grading === 'self') {
287
+ verdicts = order.criteria.map(criterion => ({ id: criterion.id, verdict: request.verdict, note: request.note }));
288
+ }
289
+ else if (policy.grading === 'checks') {
290
+ verdicts = order.criteria.map(criterion => {
291
+ const result = checks.find(row => row.criterion === criterion.id);
292
+ return { id: criterion.id, verdict: result.passed ? 'met' : 'unmet', note: `cockpit ran the check: ${result.outcome}` };
293
+ });
294
+ }
295
+ else {
296
+ const graded = await composition.evaluate({ objective: order.objective, criteria: order.criteria, nonGoals: order.non_goals, scope: order.scope, checks, changes, outOfScope });
297
+ if ('refusal' in graded)
298
+ return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
299
+ verdicts = graded.criteria;
300
+ evaluator = { tool: graded.evaluator.tool, fallbacks: graded.evaluator.fallbacks.map(row => ({ ...row })) };
301
+ }
302
+ const passed = verdicts.every(row => row.verdict === 'met') && checks.every(row => row.passed) && outOfScope.length === 0;
303
+ const grade = { at: now(), digest: record.accepted.digest, mode: policy.grading, passed,
304
+ criteria: [...verdicts], outOfScope: [...outOfScope], ...evaluator ? { evaluator } : {}, ...request.note ? { note: request.note } : {} };
305
+ await store.update(order.id, async (existing) => {
306
+ if (!existing || existing.accepted.digest !== record.accepted.digest)
307
+ throw new RefusalError(`${order.id} changed while it was graded; rerun work grade`);
308
+ return { record: { ...existing, state: passed ? 'graded' : 'accepted', attempts: existing.attempts + (passed ? 0 : 1), grade,
309
+ ...checks.length ? { checks: { at: now(), digest: record.accepted.digest, results: [...checks] } } : {} }, value: undefined };
310
+ });
311
+ const value = { id: order.id, state: passed ? 'graded' : 'accepted', mode: policy.grading, criteria: verdicts, checks, outOfScope, ...evaluator ? { evaluator } : {} };
312
+ if (passed)
313
+ return complete(operation, value);
314
+ return unsatisfied(operation, value, [...verdicts.filter(row => row.verdict !== 'met').map(row => `${row.id} ${row.verdict}: ${row.note}`),
315
+ ...checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`),
316
+ ...outOfScope.length ? [`changed outside scope: ${outOfScope.join(', ')}`] : []].join('; '));
317
+ }
318
+ if (request.operation === 'work-signoff') {
319
+ const record = await store.read(request.workOrderId);
320
+ if (!record)
321
+ return refusal(operation, `${request.workOrderId} is not accepted`);
322
+ if (TERMINAL.has(record.state))
323
+ return refusal(operation, `${request.workOrderId} is already closed (${record.state})`);
324
+ let how;
325
+ if (request.close === 'done') {
326
+ const { order } = await current(request.workOrderId);
327
+ if (record.state !== 'graded' || !record.grade?.passed || record.grade.digest !== record.accepted.digest) {
328
+ return refusal(operation, `${order.id} is ${record.state}, not graded; run npx --no cockpit work grade --config <your cockpit.toml> --work-order ${order.id}`);
329
+ }
330
+ const open = await Promise.all(order.depends_on.map(async (dependency) => (await store.read(dependency))?.state === 'done' ? undefined : dependency));
331
+ if (open.some(Boolean))
332
+ return refusal(operation, `${order.id} depends on ${open.filter(Boolean).join(', ')}, which ${open.filter(Boolean).length === 1 ? 'is' : 'are'} not done`);
333
+ how = CLOSURE[record.grade.mode];
334
+ }
335
+ else {
336
+ if (!request.note)
337
+ return refusal(operation, `closing ${request.workOrderId} as ${request.close} needs --note saying what was and was not done`);
338
+ how = request.close;
339
+ }
340
+ return gated('signoff', policy.gates.signoff, [`Close ${request.workOrderId} as ${request.close} (${how})`, ...request.note ? [`Note: ${request.note}`] : [],
341
+ ...record.grade ? record.grade.criteria.map(row => `${row.id}: ${row.verdict} — ${row.note}`) : []], 'signoff', async (by) => store.update(request.workOrderId, async (existing) => {
342
+ if (!existing || existing.state !== record.state || existing.accepted.digest !== record.accepted.digest)
343
+ throw new RefusalError(`${request.workOrderId} changed while this prompt was open`);
344
+ const state = request.close;
345
+ return { record: { ...existing, state, closure: { at: now(), by, how, ...request.note ? { note: request.note } : {} } },
346
+ value: complete(operation, { id: request.workOrderId, state, closedAs: how, by }) };
347
+ }));
348
+ }
349
+ if (request.operation === 'work-recover')
350
+ return complete(operation, await store.recover(request.workOrderId, request.apply));
351
+ if (request.operation === 'work-verify') {
352
+ const problems = [];
353
+ const awaiting = [];
354
+ for (const record of await store.list()) {
355
+ const row = await observe(record.id);
356
+ if (!TERMINAL.has(record.state) && row.digest !== record.accepted.digest)
357
+ problems.push(`${record.id} changed since it was accepted; accept the change or revert it`);
358
+ if (record.state === 'done' && (!record.grade?.passed || record.grade.digest !== record.accepted.digest || !record.closure))
359
+ problems.push(`${record.id} is done without a passing grade of its accepted contract`);
360
+ awaiting.push(...pendingCi(record).map(step => `${record.id} ${step}`));
361
+ }
362
+ const ci = runningInCi(composition.env);
363
+ const value = { ci, problems, ...ci ? { confirmed: awaiting } : { awaitingCi: awaiting } };
364
+ return problems.length ? unsatisfied(operation, value, problems.join('; ')) : complete(operation, value);
365
+ }
366
+ // grade-criteria: the quick path, an independent grade of uncommitted work with no order and no record.
367
+ const parsed = await composition.readCriteriaFile(request.criteriaFile);
368
+ if (!parsed.ok)
369
+ return refusal(operation, `${request.criteriaFile} is not a criteria file: ${parsed.problems.join('; ')}`);
370
+ const file = parsed.value;
371
+ const checks = await runChecks(file);
372
+ // The criteria file is the request, not part of the change it grades.
373
+ const changes = (await composition.changes(await composition.heads())).filter(change => change.path !== posix.normalize(request.criteriaFile));
374
+ const outOfScope = file.scope.length ? changes.map(change => change.path).filter(path => !scopeMatches(file.scope, path)) : [];
375
+ const subject = { ...file.objective ? { objective: file.objective } : {}, criteria: file.criteria, nonGoals: [], scope: file.scope, checks, changes, outOfScope };
376
+ const graded = await composition.evaluate(subject);
377
+ if ('refusal' in graded)
378
+ return refusal(operation, `${graded.refusal.code}: ${graded.refusal.message}; ${graded.refusal.fix}`);
379
+ const passed = graded.criteria.every(row => row.verdict === 'met') && checks.every(row => row.passed) && outOfScope.length === 0;
380
+ const value = { criteria: graded.criteria, checks, outOfScope, evaluator: graded.evaluator };
381
+ return passed ? complete(operation, value) : unsatisfied(operation, value, graded.criteria.filter(row => row.verdict !== 'met').map(row => `${row.id} ${row.verdict}: ${row.note}`)
382
+ .concat(checks.filter(row => !row.passed).map(row => `check ${row.criterion}: ${row.outcome}`), outOfScope.length ? [`changed outside scope: ${outOfScope.join(', ')}`] : []).join('; '));
383
+ }
@@ -0,0 +1,124 @@
1
+ import { type EvaluatorTool } from '../schema.js';
2
+ import type { ConsumedWorkEnvironmentConfig } from '../config/load.js';
3
+ import { type EffectivePolicy } from '../autonomy.js';
4
+ import { type EvaluatorChain } from '../grading/evaluate.js';
5
+ import { type ChangedFile, type GradingSubject, type Verdict } from '../grading/grade.js';
6
+ import { type BrowserCommandInput, type BrowserCommandKind, type BrowserRoutePolicy } from '../browser/commands.js';
7
+ import { type BrowserRuntimeProjection } from '../browser/runtime.js';
8
+ import { type BrowserProjectClient } from '../browser/surfaces.js';
9
+ import type { EnvironmentSnapshot, RuntimeEvidence } from '../environment/evidence.js';
10
+ import { type GateDependencies, type GateRegistry, type GateRequest } from '../testing/gates.js';
11
+ import { parseCriteriaFile, type ParsedFile, type WorkOrder, type WorkOrderCheck } from './contract.js';
12
+ import { type CheckResult, type WorkStore } from './store.js';
13
+ export type WorkEnvironmentApplicationConfig = ConsumedWorkEnvironmentConfig;
14
+ type BrowserAuthority = Readonly<{
15
+ runtime: BrowserRuntimeProjection;
16
+ client: BrowserProjectClient;
17
+ policy: BrowserRoutePolicy;
18
+ state?: BrowserCommandInput['state'];
19
+ }>;
20
+ type EvaluatorAuthority = Readonly<{
21
+ path?: string;
22
+ bytes: string;
23
+ sha256: string;
24
+ restriction: 'instructed';
25
+ executableDirectories?: readonly string[];
26
+ chain: EvaluatorChain;
27
+ environment?: Readonly<Record<string, string | undefined>>;
28
+ }>;
29
+ export interface WorkApplicationAuthorities {
30
+ readonly browser?: BrowserAuthority;
31
+ readonly resolveBrowser?: () => Promise<BrowserAuthority>;
32
+ readonly testing?: Readonly<{
33
+ registry: GateRegistry;
34
+ dependencies: GateDependencies;
35
+ runtime?: () => Promise<Readonly<{
36
+ stack: NonNullable<GateRequest['stack']>;
37
+ evidence?: Readonly<{
38
+ snapshot: EnvironmentSnapshot;
39
+ capture: () => Promise<RuntimeEvidence>;
40
+ }>;
41
+ }>>;
42
+ }>;
43
+ readonly evaluator?: EvaluatorAuthority;
44
+ readonly resolveEvaluator?: () => Promise<EvaluatorAuthority>;
45
+ readonly executableDirectories?: readonly string[];
46
+ readonly declaredEnvironment?: Readonly<Record<string, string>>;
47
+ readonly env: Readonly<Record<string, string | undefined>>;
48
+ }
49
+ export type GradingResult = Readonly<{
50
+ criteria: readonly Verdict[];
51
+ evaluator: Readonly<{
52
+ tool: string;
53
+ fallbacks: readonly Readonly<{
54
+ tool: string;
55
+ code: string;
56
+ message: string;
57
+ }>[];
58
+ }>;
59
+ }> | Readonly<{
60
+ refusal: Readonly<{
61
+ code: string;
62
+ message: string;
63
+ fix: string;
64
+ }>;
65
+ }>;
66
+ export interface WorkApplicationComposition {
67
+ readonly policy: EffectivePolicy;
68
+ readonly caps: Readonly<{
69
+ wip: number;
70
+ attempts: number;
71
+ }>;
72
+ readonly env: Readonly<Record<string, string | undefined>>;
73
+ readonly store: WorkStore;
74
+ readonly recordedPolicy: () => Promise<EffectivePolicy | undefined>;
75
+ readonly orderIds: () => Promise<readonly string[]>;
76
+ readonly readOrder: (id: string) => Promise<ParsedFile<WorkOrder>>;
77
+ readonly readCriteriaFile: (path: string) => Promise<ReturnType<typeof parseCriteriaFile>>;
78
+ readonly sourceDigests: (order: Pick<WorkOrder, 'criteria'>) => Promise<Readonly<Record<string, string>>>;
79
+ readonly heads: () => Promise<Readonly<Record<string, string>>>;
80
+ readonly runCheck: (criterion: string, check: WorkOrderCheck) => Promise<CheckResult>;
81
+ readonly changes: (base: Readonly<Record<string, string>>) => Promise<readonly ChangedFile[]>;
82
+ readonly evaluate: (subject: GradingSubject) => Promise<GradingResult>;
83
+ readonly browser: (kind: BrowserCommandKind, role?: string) => Promise<BrowserCommandInput>;
84
+ readonly gate: (request: WorkApplicationGateRequest) => Promise<Readonly<{
85
+ registry: GateRegistry;
86
+ request: GateRequest;
87
+ dependencies: GateDependencies;
88
+ }>>;
89
+ }
90
+ export interface WorkApplicationGateRequest {
91
+ readonly operation: 'test-run';
92
+ readonly gate: string;
93
+ readonly mode: 'canonical' | 'full' | 'staged';
94
+ readonly select?: Readonly<{
95
+ kind: 'spec' | 'file' | 'test' | 'route';
96
+ value: string;
97
+ }>;
98
+ }
99
+ export type VerificationObservation = Readonly<{
100
+ kind: 'executed';
101
+ exitCode: number;
102
+ stdout: string;
103
+ stderr: string;
104
+ truncated: boolean;
105
+ }> | Readonly<{
106
+ kind: 'error' | 'unavailable';
107
+ reason: string;
108
+ }>;
109
+ export declare function isWorkApplicationComposition(value: unknown): value is WorkApplicationComposition;
110
+ export type ConfiguredWorkApplicationCompositionResult = Readonly<{
111
+ kind: 'complete';
112
+ composition: WorkApplicationComposition;
113
+ }> | Readonly<{
114
+ kind: 'refusal';
115
+ reason: string;
116
+ }>;
117
+ export declare function createConfiguredWorkApplicationComposition(input: Readonly<{
118
+ configPath: string;
119
+ env: Readonly<Record<string, string | undefined>>;
120
+ }>, options?: Readonly<{
121
+ /** Grade with only this tool, with no fallback: the person named the grader for this run. */
122
+ evaluator?: EvaluatorTool;
123
+ }>): Promise<ConfiguredWorkApplicationCompositionResult>;
124
+ export {};