pi-daddy 0.22.0 → 0.23.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 (362) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/contracts/daily-view/v1/README.md +114 -0
  3. package/contracts/daily-view/v1/fixtures/view.json +525 -0
  4. package/contracts/daily-view/v1/fixtures/view.txt +75 -0
  5. package/contracts/daily-view/v1/fixtures/work.jsonl +17 -0
  6. package/contracts/daily-view/v1/p03/README.md +26 -0
  7. package/contracts/daily-view/v1/p03/fixtures/retained-executions.json +241 -0
  8. package/contracts/daily-view/v1/p03/projection.schema.json +194 -0
  9. package/contracts/daily-view/v1/p03/provenance.json +21 -0
  10. package/contracts/dashboard-host/v1/README.md +176 -0
  11. package/contracts/debrief/v1/README.md +146 -0
  12. package/contracts/debrief/v1/intervention/README.md +32 -0
  13. package/contracts/debrief/v1/intervention/fixtures/artifact-3ee11136beb864f518252e5024dae9197421fbf97b6e981d3243c782b5c44bd0.txt +1 -0
  14. package/contracts/debrief/v1/intervention/fixtures/artifact-58905d872dd3c54a9705f67b05fa82edbad7af6476ae8993faf1a7b9fadfb905.txt +1 -0
  15. package/contracts/debrief/v1/intervention/fixtures/assessment.json +29 -0
  16. package/contracts/debrief/v1/intervention/fixtures/blind-view.json +20 -0
  17. package/contracts/debrief/v1/intervention/fixtures/choice.json +7 -0
  18. package/contracts/debrief/v1/intervention/fixtures/manifest.json +57 -0
  19. package/contracts/debrief/v1/intervention/fixtures/reveal.json +39 -0
  20. package/contracts/debrief/v1/provenance.json +89 -0
  21. package/contracts/debrief/v1/work-capture/README.md +38 -0
  22. package/contracts/debrief/v1/work-capture/fixtures/batch.json +17 -0
  23. package/contracts/debrief/v1/work-capture/fixtures/cases.json +184 -0
  24. package/contracts/debrief/v1/work-capture/fixtures/review.json +184 -0
  25. package/contracts/debrief/v1/work-capture/review-request.schema.json +41 -0
  26. package/contracts/debrief/v1/work-capture/work-case.schema.json +190 -0
  27. package/contracts/debrief/v2/README.md +71 -0
  28. package/contracts/debrief/v2/input-pin.json +66 -0
  29. package/contracts/debrief/v2/intervention-upstream.md +40 -0
  30. package/contracts/debrief/v2/work-signals-upstream.md +22 -0
  31. package/contracts/dispatch-control/v1/README.md +109 -0
  32. package/contracts/dispatch-control/v1/request.schema.json +19 -0
  33. package/contracts/effect-profile/v1/README.md +124 -0
  34. package/contracts/execution-retention/v1/README.md +109 -0
  35. package/contracts/execution-retention/v2/README.md +148 -0
  36. package/contracts/execution-retention/v2/fixtures/interrupted-native-bytes.json +91 -0
  37. package/contracts/execution-retention/v2/fixtures/missing-native-session.json +91 -0
  38. package/contracts/execution-retention/v2/fixtures/native-file-unknown-branch.json +91 -0
  39. package/contracts/execution-retention/v2/fixtures/native-id-without-bytes.json +91 -0
  40. package/contracts/execution-retention/v2/fixtures/native-live-branch.json +90 -0
  41. package/contracts/execution-retention/v2/fixtures/replaced-native-session.json +92 -0
  42. package/contracts/execution-retention/v2/fixtures/result-82c620cdfeb8ca6b858cf653d7f5e2eedca5315d6244216d60e8b36f03223d6a.bin +1 -0
  43. package/contracts/execution-retention/v2/fixtures/session-2ec1f134bc739984434f7fca689e1c3d1c39fce2dcadae709e9b6faedfcc4ac7.bin +3 -0
  44. package/contracts/execution-retention/v2/fixtures/session-9bdb4bd16a901da31580acf056bda525225259e15ec409e0410480109011772f.bin +3 -0
  45. package/contracts/execution-retention/v2/manifest.schema.json +858 -0
  46. package/contracts/experiment/v1/README.md +164 -0
  47. package/contracts/factory-order/v1/README.md +151 -0
  48. package/contracts/factory-order/v1/adoption-pin.json +8 -0
  49. package/contracts/intent-control/v1/README.md +135 -0
  50. package/contracts/intent-control/v1/request.schema.json +1096 -0
  51. package/contracts/intent-control/v2/README.md +34 -0
  52. package/contracts/intent-control/v2/request.schema.json +1070 -0
  53. package/contracts/ledger/v4/README.md +241 -0
  54. package/contracts/ledger/v4/fixtures/layout-options.json +428 -0
  55. package/contracts/ledger/v4/fixtures/work-acceptance.json +57 -0
  56. package/contracts/ledger/v4/fixtures/work-occurrence.json +44 -0
  57. package/contracts/ledger/v4/fixtures/work-revision.json +26 -0
  58. package/contracts/ledger/v4/fixtures/work-snapshot.json +73 -0
  59. package/contracts/ledger/v4/ledger-event.schema.json +162 -0
  60. package/contracts/ordinary-control/v1/README.md +59 -0
  61. package/contracts/producer-ipc/v1/README.md +137 -0
  62. package/dist/check-runner.d.ts +5 -0
  63. package/dist/check-runner.d.ts.map +1 -1
  64. package/dist/check-runner.js +16 -2
  65. package/dist/check-runner.js.map +1 -1
  66. package/dist/control-journal.d.ts +20 -0
  67. package/dist/control-journal.d.ts.map +1 -0
  68. package/dist/control-journal.js +73 -0
  69. package/dist/control-journal.js.map +1 -0
  70. package/dist/daily-view-input.d.ts +44 -0
  71. package/dist/daily-view-input.d.ts.map +1 -0
  72. package/dist/daily-view-input.js +68 -0
  73. package/dist/daily-view-input.js.map +1 -0
  74. package/dist/daily-view-render.d.ts +4 -0
  75. package/dist/daily-view-render.d.ts.map +1 -0
  76. package/dist/daily-view-render.js +41 -0
  77. package/dist/daily-view-render.js.map +1 -0
  78. package/dist/daily-view.d.ts +74 -0
  79. package/dist/daily-view.d.ts.map +1 -0
  80. package/dist/daily-view.js +170 -0
  81. package/dist/daily-view.js.map +1 -0
  82. package/dist/dashboard-cli.d.ts +26 -1
  83. package/dist/dashboard-cli.d.ts.map +1 -1
  84. package/dist/dashboard-cli.js +121 -3
  85. package/dist/dashboard-cli.js.map +1 -1
  86. package/dist/dashboard-harness.d.ts +19 -0
  87. package/dist/dashboard-harness.d.ts.map +1 -0
  88. package/dist/dashboard-harness.js +75 -0
  89. package/dist/dashboard-harness.js.map +1 -0
  90. package/dist/dashboard-herdr.d.ts.map +1 -1
  91. package/dist/dashboard-herdr.js +7 -2
  92. package/dist/dashboard-herdr.js.map +1 -1
  93. package/dist/dashboard-host-contract.d.ts +111 -0
  94. package/dist/dashboard-host-contract.d.ts.map +1 -0
  95. package/dist/dashboard-host-contract.js +14 -0
  96. package/dist/dashboard-host-contract.js.map +1 -0
  97. package/dist/dashboard-host-transport.d.ts +15 -0
  98. package/dist/dashboard-host-transport.d.ts.map +1 -0
  99. package/dist/dashboard-host-transport.js +122 -0
  100. package/dist/dashboard-host-transport.js.map +1 -0
  101. package/dist/dashboard-host.d.ts +149 -0
  102. package/dist/dashboard-host.d.ts.map +1 -0
  103. package/dist/dashboard-host.js +329 -0
  104. package/dist/dashboard-host.js.map +1 -0
  105. package/dist/dashboard-observation.d.ts +28 -0
  106. package/dist/dashboard-observation.d.ts.map +1 -0
  107. package/dist/dashboard-observation.js +86 -0
  108. package/dist/dashboard-observation.js.map +1 -0
  109. package/dist/debrief-contract.d.ts +65 -0
  110. package/dist/debrief-contract.d.ts.map +1 -0
  111. package/dist/debrief-contract.js +151 -0
  112. package/dist/debrief-contract.js.map +1 -0
  113. package/dist/debrief-fixture.d.ts +16 -0
  114. package/dist/debrief-fixture.d.ts.map +1 -0
  115. package/dist/debrief-fixture.js +41 -0
  116. package/dist/debrief-fixture.js.map +1 -0
  117. package/dist/debrief-host.d.ts +59 -0
  118. package/dist/debrief-host.d.ts.map +1 -0
  119. package/dist/debrief-host.js +49 -0
  120. package/dist/debrief-host.js.map +1 -0
  121. package/dist/debrief-render.d.ts +5 -0
  122. package/dist/debrief-render.d.ts.map +1 -0
  123. package/dist/debrief-render.js +65 -0
  124. package/dist/debrief-render.js.map +1 -0
  125. package/dist/debrief.d.ts +90 -0
  126. package/dist/debrief.d.ts.map +1 -0
  127. package/dist/debrief.js +263 -0
  128. package/dist/debrief.js.map +1 -0
  129. package/dist/delegate-types.d.ts +2 -0
  130. package/dist/delegate-types.d.ts.map +1 -1
  131. package/dist/delegate-types.js.map +1 -1
  132. package/dist/delegate.d.ts.map +1 -1
  133. package/dist/delegate.js +1 -0
  134. package/dist/delegate.js.map +1 -1
  135. package/dist/dispatch-control.d.ts +45 -0
  136. package/dist/dispatch-control.d.ts.map +1 -0
  137. package/dist/dispatch-control.js +76 -0
  138. package/dist/dispatch-control.js.map +1 -0
  139. package/dist/effect-profile-runtime.d.ts +40 -0
  140. package/dist/effect-profile-runtime.d.ts.map +1 -0
  141. package/dist/effect-profile-runtime.js +51 -0
  142. package/dist/effect-profile-runtime.js.map +1 -0
  143. package/dist/effect-profile.d.ts +40 -0
  144. package/dist/effect-profile.d.ts.map +1 -0
  145. package/dist/effect-profile.js +152 -0
  146. package/dist/effect-profile.js.map +1 -0
  147. package/dist/execution-retention.d.ts +90 -0
  148. package/dist/execution-retention.d.ts.map +1 -0
  149. package/dist/execution-retention.js +247 -0
  150. package/dist/execution-retention.js.map +1 -0
  151. package/dist/experiment-contract.d.ts +56 -0
  152. package/dist/experiment-contract.d.ts.map +1 -0
  153. package/dist/experiment-contract.js +80 -0
  154. package/dist/experiment-contract.js.map +1 -0
  155. package/dist/experiment-state.d.ts +22 -0
  156. package/dist/experiment-state.d.ts.map +1 -0
  157. package/dist/experiment-state.js +79 -0
  158. package/dist/experiment-state.js.map +1 -0
  159. package/dist/experiment-store.d.ts +28 -0
  160. package/dist/experiment-store.d.ts.map +1 -0
  161. package/dist/experiment-store.js +130 -0
  162. package/dist/experiment-store.js.map +1 -0
  163. package/dist/experiment.d.ts +65 -0
  164. package/dist/experiment.d.ts.map +1 -0
  165. package/dist/experiment.js +357 -0
  166. package/dist/experiment.js.map +1 -0
  167. package/dist/factory-contract.d.ts +79 -0
  168. package/dist/factory-contract.d.ts.map +1 -0
  169. package/dist/factory-contract.js +89 -0
  170. package/dist/factory-contract.js.map +1 -0
  171. package/dist/factory-migration.d.ts +20 -0
  172. package/dist/factory-migration.d.ts.map +1 -0
  173. package/dist/factory-migration.js +55 -0
  174. package/dist/factory-migration.js.map +1 -0
  175. package/dist/factory-order.d.ts +848 -0
  176. package/dist/factory-order.d.ts.map +1 -0
  177. package/dist/factory-order.js +83 -0
  178. package/dist/factory-order.js.map +1 -0
  179. package/dist/factory-registry.d.ts +121 -0
  180. package/dist/factory-registry.d.ts.map +1 -0
  181. package/dist/factory-registry.js +133 -0
  182. package/dist/factory-registry.js.map +1 -0
  183. package/dist/file-lock.d.ts +8 -2
  184. package/dist/file-lock.d.ts.map +1 -1
  185. package/dist/file-lock.js +78 -19
  186. package/dist/file-lock.js.map +1 -1
  187. package/dist/herdr-poll.d.ts +8 -0
  188. package/dist/herdr-poll.d.ts.map +1 -1
  189. package/dist/herdr-poll.js +23 -0
  190. package/dist/herdr-poll.js.map +1 -1
  191. package/dist/index.d.ts +20 -0
  192. package/dist/index.d.ts.map +1 -1
  193. package/dist/index.js +20 -0
  194. package/dist/index.js.map +1 -1
  195. package/dist/intent-application.d.ts +17 -0
  196. package/dist/intent-application.d.ts.map +1 -0
  197. package/dist/intent-application.js +113 -0
  198. package/dist/intent-application.js.map +1 -0
  199. package/dist/intent-control.d.ts +201 -0
  200. package/dist/intent-control.d.ts.map +1 -0
  201. package/dist/intent-control.js +110 -0
  202. package/dist/intent-control.js.map +1 -0
  203. package/dist/intent-scheduling.d.ts +9 -0
  204. package/dist/intent-scheduling.d.ts.map +1 -0
  205. package/dist/intent-scheduling.js +15 -0
  206. package/dist/intent-scheduling.js.map +1 -0
  207. package/dist/ledger-append.d.ts +15 -0
  208. package/dist/ledger-append.d.ts.map +1 -0
  209. package/dist/ledger-append.js +94 -0
  210. package/dist/ledger-append.js.map +1 -0
  211. package/dist/ledger.d.ts +11 -0
  212. package/dist/ledger.d.ts.map +1 -1
  213. package/dist/ledger.js +3 -7
  214. package/dist/ledger.js.map +1 -1
  215. package/dist/native-session-target.d.ts +9 -0
  216. package/dist/native-session-target.d.ts.map +1 -0
  217. package/dist/native-session-target.js +29 -0
  218. package/dist/native-session-target.js.map +1 -0
  219. package/dist/native-session.d.ts +59 -0
  220. package/dist/native-session.d.ts.map +1 -0
  221. package/dist/native-session.js +154 -0
  222. package/dist/native-session.js.map +1 -0
  223. package/dist/order-schedule.d.ts +37 -0
  224. package/dist/order-schedule.d.ts.map +1 -0
  225. package/dist/order-schedule.js +81 -0
  226. package/dist/order-schedule.js.map +1 -0
  227. package/dist/ordinary-children.d.ts +52 -0
  228. package/dist/ordinary-children.d.ts.map +1 -0
  229. package/dist/ordinary-children.js +93 -0
  230. package/dist/ordinary-children.js.map +1 -0
  231. package/dist/producer-ipc-contract.d.ts +29 -0
  232. package/dist/producer-ipc-contract.d.ts.map +1 -0
  233. package/dist/producer-ipc-contract.js +29 -0
  234. package/dist/producer-ipc-contract.js.map +1 -0
  235. package/dist/producer-ipc.d.ts +55 -0
  236. package/dist/producer-ipc.d.ts.map +1 -0
  237. package/dist/producer-ipc.js +143 -0
  238. package/dist/producer-ipc.js.map +1 -0
  239. package/dist/resource-budget.d.ts +334 -0
  240. package/dist/resource-budget.d.ts.map +1 -0
  241. package/dist/resource-budget.js +415 -0
  242. package/dist/resource-budget.js.map +1 -0
  243. package/dist/retention-contract.d.ts +73 -0
  244. package/dist/retention-contract.d.ts.map +1 -0
  245. package/dist/retention-contract.js +104 -0
  246. package/dist/retention-contract.js.map +1 -0
  247. package/dist/retention-json.d.ts +3 -0
  248. package/dist/retention-json.d.ts.map +1 -0
  249. package/dist/retention-json.js +32 -0
  250. package/dist/retention-json.js.map +1 -0
  251. package/dist/run-child.d.ts +4 -0
  252. package/dist/run-child.d.ts.map +1 -1
  253. package/dist/run-child.js +16 -2
  254. package/dist/run-child.js.map +1 -1
  255. package/dist/run-herdr.d.ts +6 -0
  256. package/dist/run-herdr.d.ts.map +1 -1
  257. package/dist/run-herdr.js +19 -6
  258. package/dist/run-herdr.js.map +1 -1
  259. package/dist/vendor/adoption.d.ts +129 -0
  260. package/dist/vendor/adoption.d.ts.map +1 -0
  261. package/dist/vendor/adoption.js +102 -0
  262. package/dist/vendor/adoption.js.map +1 -0
  263. package/dist/work-ledger-destination.d.ts +20 -0
  264. package/dist/work-ledger-destination.d.ts.map +1 -0
  265. package/dist/work-ledger-destination.js +131 -0
  266. package/dist/work-ledger-destination.js.map +1 -0
  267. package/dist/work-ledger-json.d.ts +25 -0
  268. package/dist/work-ledger-json.d.ts.map +1 -0
  269. package/dist/work-ledger-json.js +267 -0
  270. package/dist/work-ledger-json.js.map +1 -0
  271. package/dist/work-ledger-occurrences.d.ts +227 -0
  272. package/dist/work-ledger-occurrences.d.ts.map +1 -0
  273. package/dist/work-ledger-occurrences.js +115 -0
  274. package/dist/work-ledger-occurrences.js.map +1 -0
  275. package/dist/work-ledger-projection.d.ts +295 -0
  276. package/dist/work-ledger-projection.d.ts.map +1 -0
  277. package/dist/work-ledger-projection.js +237 -0
  278. package/dist/work-ledger-projection.js.map +1 -0
  279. package/dist/work-ledger-snapshot.d.ts +29 -0
  280. package/dist/work-ledger-snapshot.d.ts.map +1 -0
  281. package/dist/work-ledger-snapshot.js +267 -0
  282. package/dist/work-ledger-snapshot.js.map +1 -0
  283. package/dist/work-ledger-types.d.ts +261 -0
  284. package/dist/work-ledger-types.d.ts.map +1 -0
  285. package/dist/work-ledger-types.js +13 -0
  286. package/dist/work-ledger-types.js.map +1 -0
  287. package/dist/work-ledger-validation.d.ts +14 -0
  288. package/dist/work-ledger-validation.d.ts.map +1 -0
  289. package/dist/work-ledger-validation.js +226 -0
  290. package/dist/work-ledger-validation.js.map +1 -0
  291. package/dist/work-ledger.d.ts +43 -0
  292. package/dist/work-ledger.d.ts.map +1 -0
  293. package/dist/work-ledger.js +225 -0
  294. package/dist/work-ledger.js.map +1 -0
  295. package/extensions/delegate-chain.ts +1 -0
  296. package/extensions/delegation-native.ts +12 -0
  297. package/extensions/delegation.ts +4 -3
  298. package/extensions/execute-child.ts +34 -4
  299. package/extensions/grants.ts +2 -0
  300. package/extensions/ordinary-runtime.ts +14 -0
  301. package/extensions/run-delegation.ts +7 -7
  302. package/extensions/session.ts +3 -1
  303. package/package.json +78 -4
  304. package/src/check-runner.ts +20 -2
  305. package/src/control-journal.ts +26 -0
  306. package/src/daily-view-input.ts +59 -0
  307. package/src/daily-view-render.ts +44 -0
  308. package/src/daily-view.ts +152 -0
  309. package/src/dashboard-cli.ts +103 -4
  310. package/src/dashboard-harness.ts +28 -0
  311. package/src/dashboard-herdr.ts +7 -2
  312. package/src/dashboard-host-contract.ts +42 -0
  313. package/src/dashboard-host-transport.ts +38 -0
  314. package/src/dashboard-host.ts +168 -0
  315. package/src/dashboard-observation.ts +44 -0
  316. package/src/debrief-contract.ts +127 -0
  317. package/src/debrief-fixture.ts +29 -0
  318. package/src/debrief-host.ts +53 -0
  319. package/src/debrief-render.ts +49 -0
  320. package/src/debrief.ts +198 -0
  321. package/src/delegate-types.ts +2 -0
  322. package/src/delegate.ts +1 -0
  323. package/src/dispatch-control.ts +81 -0
  324. package/src/effect-profile-runtime.ts +46 -0
  325. package/src/effect-profile.ts +129 -0
  326. package/src/execution-retention.ts +230 -0
  327. package/src/experiment-contract.ts +66 -0
  328. package/src/experiment-state.ts +44 -0
  329. package/src/experiment-store.ts +90 -0
  330. package/src/experiment.ts +231 -0
  331. package/src/factory-contract.ts +47 -0
  332. package/src/factory-migration.ts +29 -0
  333. package/src/factory-order.ts +56 -0
  334. package/src/factory-registry.ts +60 -0
  335. package/src/file-lock.ts +47 -11
  336. package/src/herdr-poll.ts +18 -0
  337. package/src/index.ts +32 -0
  338. package/src/intent-application.ts +90 -0
  339. package/src/intent-control.ts +107 -0
  340. package/src/intent-scheduling.ts +14 -0
  341. package/src/ledger-append.ts +72 -0
  342. package/src/ledger.ts +3 -9
  343. package/src/native-session-target.ts +23 -0
  344. package/src/native-session.ts +139 -0
  345. package/src/order-schedule.ts +42 -0
  346. package/src/ordinary-children.ts +59 -0
  347. package/src/producer-ipc-contract.ts +26 -0
  348. package/src/producer-ipc.ts +107 -0
  349. package/src/resource-budget.ts +349 -0
  350. package/src/retention-contract.ts +84 -0
  351. package/src/retention-json.ts +20 -0
  352. package/src/run-child.ts +13 -2
  353. package/src/run-herdr.ts +11 -7
  354. package/src/vendor/adoption.ts +109 -0
  355. package/src/work-ledger-destination.ts +113 -0
  356. package/src/work-ledger-json.ts +228 -0
  357. package/src/work-ledger-occurrences.ts +99 -0
  358. package/src/work-ledger-projection.ts +262 -0
  359. package/src/work-ledger-snapshot.ts +246 -0
  360. package/src/work-ledger-types.ts +229 -0
  361. package/src/work-ledger-validation.ts +174 -0
  362. package/src/work-ledger.ts +186 -0
@@ -0,0 +1,228 @@
1
+ import { createHash } from "node:crypto";
2
+ import { WorkInputError, type WorkFrozen, type WorkProblem, type WorkConflict } from "./work-ledger-types.ts";
3
+
4
+ export const WORK_EVENT_BYTES = 64 * 1024;
5
+ export const WORK_TEXT_BYTES = 16 * 1024 * 1024;
6
+ export const WORK_RECORDS = 10_000;
7
+ const MAX_DEPTH = 16;
8
+ const MAX_ARRAY = 256;
9
+ export type WorkJson = null | boolean | number | string | WorkJson[] | { [key: string]: WorkJson };
10
+ const invalid = (): never => { throw new WorkInputError("WORK_SCHEMA_INVALID"); };
11
+ const limit = (): never => { throw new WorkInputError("WORK_LIMIT_EXCEEDED"); };
12
+
13
+ function unicode(value: string): string {
14
+ // RFC 8785 refuses lone UTF-16 surrogates, including ones decoded from escapes.
15
+ for (let i = 0; i < value.length; i++) {
16
+ const code = value.charCodeAt(i);
17
+ if (code >= 0xd800 && code <= 0xdbff) {
18
+ const next = value.charCodeAt(++i);
19
+ if (!(next >= 0xdc00 && next <= 0xdfff)) invalid();
20
+ } else if (code >= 0xdc00 && code <= 0xdfff) invalid();
21
+ }
22
+ return value;
23
+ }
24
+
25
+ /** Inspect descriptors rather than serializing: no getters/toJSON or lossy JSON coercion. */
26
+ export function ownWorkFields(value: unknown): Record<string, PropertyDescriptor> {
27
+ if (value === null || typeof value !== "object" || Array.isArray(value)) invalid();
28
+ const prototype = Object.getPrototypeOf(value);
29
+ if (prototype !== Object.prototype && prototype !== null) invalid();
30
+ const descriptors = Object.getOwnPropertyDescriptors(value);
31
+ for (const key of Reflect.ownKeys(descriptors)) {
32
+ if (typeof key !== "string") return invalid();
33
+ const descriptor = descriptors[key];
34
+ if (!Object.hasOwn(descriptor, "value") || !descriptor.enumerable) invalid();
35
+ unicode(key);
36
+ }
37
+ return descriptors;
38
+ }
39
+
40
+ /** Detach only the approved JSON value profile, before any serialization or validation use. */
41
+ export function copyWorkJson(value: unknown): WorkJson {
42
+ const ancestors = new Set<object>();
43
+ function copy(v: unknown, depth: number): WorkJson {
44
+ if (v === null || typeof v === "boolean") return v;
45
+ if (typeof v === "string") return unicode(v);
46
+ if (typeof v === "number") return Number.isSafeInteger(v) ? v : invalid();
47
+ if (typeof v !== "object") return invalid();
48
+ if (depth > MAX_DEPTH) limit();
49
+ if (ancestors.has(v)) invalid();
50
+ ancestors.add(v);
51
+ let result: WorkJson;
52
+ if (Array.isArray(v)) {
53
+ if (Object.getPrototypeOf(v) !== Array.prototype) invalid();
54
+ if (v.length > MAX_ARRAY) limit();
55
+ const fields = Object.getOwnPropertyDescriptors(v);
56
+ if (Reflect.ownKeys(fields).length !== v.length + 1) invalid();
57
+ result = [];
58
+ for (let i = 0; i < v.length; i++) {
59
+ const d = fields[String(i)];
60
+ if (!d || !Object.hasOwn(d, "value") || !d.enumerable) invalid();
61
+ result.push(copy(d.value, depth + 1));
62
+ }
63
+ } else {
64
+ const fields = ownWorkFields(v);
65
+ result = {};
66
+ for (const key of Object.keys(fields)) {
67
+ Object.defineProperty(result, key, { value: copy(fields[key].value, depth + 1), enumerable: true, writable: true, configurable: true });
68
+ }
69
+ }
70
+ ancestors.delete(v);
71
+ return result;
72
+ }
73
+ return copy(value, 1);
74
+ }
75
+
76
+ /** Exact decimal arithmetic BEFORE Number conversion, without unbounded exponent allocation. */
77
+ function integerToken(token: string): number {
78
+ const match = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(token)!;
79
+ let digits = (match[2] + (match[3] ?? "")).replace(/^0+/, "");
80
+ if (!digits) return 0;
81
+ let shift = BigInt(match[4] ?? "0") - BigInt((match[3] ?? "").length);
82
+ const trailing = /0*$/.exec(digits)![0].length;
83
+ digits = digits.slice(0, digits.length - trailing);
84
+ shift += BigInt(trailing);
85
+ if (shift < 0n || BigInt(digits.length) + shift > 16n) invalid();
86
+ const exact = BigInt(digits) * (10n ** shift);
87
+ if (exact > BigInt(Number.MAX_SAFE_INTEGER)) invalid();
88
+ return Number(match[1] === "-" ? -exact : exact);
89
+ }
90
+
91
+ type JsonNode =
92
+ | { type: "literal"; value: null | boolean | string }
93
+ | { type: "number"; token: string }
94
+ | { type: "array"; items: JsonNode[] }
95
+ | { type: "object"; entries: Array<[string, JsonNode]> };
96
+
97
+ function checkMembers(node: JsonNode): void {
98
+ if (node.type === "object") {
99
+ const names = new Set<string>();
100
+ for (const [key, child] of node.entries) {
101
+ if (names.has(key)) throw new WorkInputError("WORK_DUPLICATE_MEMBER");
102
+ names.add(key);
103
+ checkMembers(child);
104
+ }
105
+ } else if (node.type === "array") node.items.forEach(checkMembers);
106
+ }
107
+ function materialize(node: JsonNode): WorkJson {
108
+ if (node.type === "number") return integerToken(node.token);
109
+ if (node.type === "literal") return typeof node.value === "string" ? unicode(node.value) : node.value;
110
+ if (node.type === "array") return node.items.map(materialize);
111
+ return Object.fromEntries(node.entries.map(([key, child]) => [unicode(key), materialize(child)]));
112
+ }
113
+
114
+ /** Bounded syntax tree: syntax, decoded duplicates, then exact numeric/string profile. */
115
+ export function parseWorkJson(text: string): WorkJson {
116
+ if (typeof text !== "string") invalid();
117
+ if (Buffer.byteLength(text, "utf8") > WORK_EVENT_BYTES) limit();
118
+ let at = 0;
119
+ const syntax = (): never => { throw new WorkInputError("WORK_JSON_INVALID"); };
120
+ const whitespace = () => { while (at < text.length && /[ \t\r\n]/.test(text[at])) at++; };
121
+ function string(): string {
122
+ const start = at++;
123
+ while (at < text.length) {
124
+ const char = text[at++];
125
+ if (char === '"') {
126
+ // JSON.parse is used ONLY for one lexically delimited string token, never an object.
127
+ return JSON.parse(text.slice(start, at)) as string;
128
+ }
129
+ if (char.charCodeAt(0) < 0x20) syntax();
130
+ if (char === "\\") {
131
+ const escape = text[at++];
132
+ if (escape === "u") {
133
+ if (!/^[0-9a-fA-F]{4}$/.test(text.slice(at, at + 4))) syntax();
134
+ at += 4;
135
+ } else if (!escape || !'"\\/bfnrt'.includes(escape)) syntax();
136
+ }
137
+ }
138
+ return syntax();
139
+ }
140
+ function value(depth: number): JsonNode {
141
+ whitespace();
142
+ const char = text[at];
143
+ if (char === '"') return { type: "literal", value: string() };
144
+ if (char === "{" || char === "[") {
145
+ if (depth > MAX_DEPTH) limit();
146
+ at++;
147
+ whitespace();
148
+ const object = char === "{";
149
+ const end = object ? "}" : "]";
150
+ const entries: Array<[string, JsonNode]> = [];
151
+ const items: JsonNode[] = [];
152
+ if (text[at] !== end) for (;;) {
153
+ whitespace();
154
+ let key = "";
155
+ if (object) {
156
+ if (text[at] !== '"') syntax();
157
+ key = string();
158
+ whitespace();
159
+ if (text[at++] !== ":") syntax();
160
+ } else if (items.length >= MAX_ARRAY) limit();
161
+ const child = value(depth + 1);
162
+ if (object) entries.push([key, child]); else items.push(child);
163
+ whitespace();
164
+ if (text[at] === end) break;
165
+ if (text[at++] !== ",") syntax();
166
+ }
167
+ at++;
168
+ return object ? { type: "object", entries } : { type: "array", items };
169
+ }
170
+ for (const [token, literal] of [["null", null], ["true", true], ["false", false]] as const) {
171
+ if (text.startsWith(token, at)) { at += token.length; return { type: "literal", value: literal }; }
172
+ }
173
+ const token = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(text.slice(at));
174
+ if (!token) return syntax();
175
+ at += token[0].length;
176
+ return { type: "number", token: token[0] };
177
+ }
178
+ const result = value(1);
179
+ whitespace();
180
+ if (at !== text.length) syntax();
181
+ checkMembers(result);
182
+ return materialize(result);
183
+ }
184
+
185
+ function emit(value: WorkJson): string {
186
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
187
+ if (Array.isArray(value)) return `[${value.map(emit).join(",")}]`;
188
+ // Emit keys directly: reconstructing a JS object would reorder numeric-looking keys.
189
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${emit(value[key])}`).join(",")}}`;
190
+ }
191
+
192
+ /** RFC 8785 emission restricted to our validated JSON profile; not an archival-byte identity. */
193
+ export function canonicalWorkJson(value: unknown): string { return emit(copyWorkJson(value)); }
194
+ export function workDigest(value: unknown): string {
195
+ return createHash("sha256").update(canonicalWorkJson(value), "utf8").digest("hex");
196
+ }
197
+ /** @internal ONLY validated constituents / internally constructed plain results. Output collections
198
+ * are not wire records: they may contain >256 claims, problems, labels, or conflict alternatives.
199
+ * Never use this emitter in place of copyWorkJson/strict ingestion at an input boundary. */
200
+ export function workResultKey<T>(value: T): string { return emit(value as unknown as WorkJson); }
201
+ export function sortWorkResults<T>(values: Iterable<T>): T[] {
202
+ const rows = new Map([...values].map(value => [workResultKey(value), value]));
203
+ return [...rows].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, value]) => value);
204
+ }
205
+ export function mergeWorkProblems(values: readonly WorkFrozen<WorkProblem>[]): WorkFrozen<WorkProblem>[] {
206
+ const rows = new Map<string, WorkFrozen<WorkProblem>>();
207
+ for (const row of values) {
208
+ const id = workResultKey([row.code, row.reference]), prior = rows.get(id);
209
+ rows.set(id, { ...row, affectedObligations: sortWorkResults([...(prior?.affectedObligations ?? []), ...row.affectedObligations]) });
210
+ }
211
+ return sortWorkResults(rows.values());
212
+ }
213
+ export function mergeWorkConflicts(values: readonly WorkFrozen<WorkConflict>[]): WorkFrozen<WorkConflict>[] {
214
+ const rows = new Map<string, WorkFrozen<WorkConflict>>();
215
+ for (const row of values) {
216
+ const id = workResultKey([row.kind, row.id]), prior = rows.get(id);
217
+ rows.set(id, { ...row, digests: sortWorkResults([...(prior?.digests ?? []), ...row.digests]),
218
+ affectedObligations: sortWorkResults([...(prior?.affectedObligations ?? []), ...row.affectedObligations]) });
219
+ }
220
+ return sortWorkResults(rows.values());
221
+ }
222
+ export function freezeWork<T>(value: T): WorkFrozen<T> {
223
+ if (value !== null && typeof value === "object") {
224
+ for (const child of Object.values(value)) freezeWork(child);
225
+ Object.freeze(value);
226
+ }
227
+ return value as WorkFrozen<T>;
228
+ }
@@ -0,0 +1,99 @@
1
+ import { workResultKey as key, sortWorkResults as sorted, mergeWorkProblems } from "./work-ledger-json.ts";
2
+ import { workEventConflictSources, type indexWorkLedgerText } from "./work-ledger-projection.ts";
3
+ import type {
4
+ WorkFrozen, WorkSnapshot, WorkOccurrenceEvent, WorkOccurrencePayload, WorkAttempt,
5
+ WorkAttemptBinding, WorkRuntime, WorkProblem, WorkConflict,
6
+ } from "./work-ledger-types.ts";
7
+
8
+ type Occurrence = WorkFrozen<WorkOccurrenceEvent>;
9
+ type Problem = WorkFrozen<WorkProblem>;
10
+ const eventRef = (e: Occurrence) => ({ eventId: e.eventId, digest: e.digest });
11
+ const emptyLabels = (): WorkOccurrencePayload["labels"] => ({ sessionId: null, branchLeafId: null,
12
+ toolCallId: null, taskId: null, workspaceId: null, definitionDigest: null, configurationDigest: null, modelId: null, effortId: null });
13
+
14
+ /** @internal One governed execution identity, many explicit work associations. No acceptance inferred. */
15
+ export function foldWorkOccurrences(index: ReturnType<typeof indexWorkLedgerText>, snapshot: WorkFrozen<WorkSnapshot>) {
16
+ const groups = new Map<string, Occurrence[]>();
17
+ const effectiveRefs = new Set(index.effectiveEvents.map(e => key({ eventId: e.eventId, digest: e.digest })));
18
+ for (const event of index.candidates) if (event.event === "work_occurrence") {
19
+ const group = groups.get(event.payload.executionId) ?? [];
20
+ group.push(event); groups.set(event.payload.executionId, group);
21
+ }
22
+ const effective = new Map([...groups].map(([id, group]) => [id, group.filter(e => effectiveRefs.has(key(eventRef(e))))]));
23
+ const selectedObligations = new Set(snapshot.bindings.map(b => key(b.obligation)));
24
+ const selectedJoin = (e: Occurrence) => key(e.payload.scope) === key(snapshot.scope) && selectedObligations.has(key(e.payload.obligation));
25
+ const attempts: WorkFrozen<WorkAttempt>[] = [], occurrences: WorkFrozen<WorkRuntime>["occurrences"][number][] = [];
26
+ const allProblems: Problem[] = [], conflicts: WorkFrozen<WorkConflict>[] = [];
27
+ const eventProblems = new Map<string, readonly Problem[]>();
28
+ for (const [executionId, candidates] of groups) {
29
+ const clean = effective.get(executionId)!;
30
+ const bindings = new Map<string, WorkFrozen<WorkAttemptBinding>>();
31
+ for (const event of clean.filter(selectedJoin)) {
32
+ const p = event.payload, id = key([p.scope, p.obligation]), prior = bindings.get(id);
33
+ bindings.set(id, { scope: p.scope, obligation: p.obligation,
34
+ variantIds: sorted([...(prior?.variantIds ?? []), ...(p.variantId ? [p.variantId] : [])]),
35
+ artifacts: sorted([...(prior?.artifacts ?? []), ...(p.artifact ? [p.artifact] : [])]),
36
+ });
37
+ }
38
+ const affected = sorted([...bindings.values()].map(b => b.obligation));
39
+ const problems: Problem[] = [];
40
+ const problem = (code: WorkProblem["code"], id = executionId) => problems.push({ code, reference: { type: "execution", id }, affectedObligations: affected });
41
+ const badSources = sorted(candidates.flatMap(e => workEventConflictSources(index, e)));
42
+ for (const ref of badSources) problems.push({ code: "EVENT_CONFLICT", reference: { type: "event", ref }, affectedObligations: affected });
43
+ if (bindings.size) for (const id of sorted(badSources.map(ref => ref.eventId))) {
44
+ conflicts.push({ kind: "event", id, digests: index.conflictingEvents.filter(ref => ref.eventId === id).map(ref => ref.digest), affectedObligations: affected });
45
+ }
46
+ const parents = sorted(clean.map(e => e.payload.parentExecutionId));
47
+ const children = sorted(clean.flatMap(e => e.payload.childId ? [e.payload.childId] : []));
48
+ const identityConflict = parents.length > 1 || children.length > 1;
49
+ if (identityConflict) problem("OCCURRENCE_CONFLICT");
50
+ let parentExecutionId = parents.length === 1 ? parents[0] : null;
51
+ let childId = children.length === 1 ? children[0] : null;
52
+ const observed = clean.filter(e => e.payload.provenance === "observed");
53
+ const declaredLabels = sorted(clean.filter(e => e.payload.provenance === "declared").map(e => e.payload.labels));
54
+ const observedLabels = sorted(observed.map(e => e.payload.labels));
55
+ const effectiveLabels = emptyLabels();
56
+ for (const label of Object.keys(effectiveLabels) as Array<keyof WorkOccurrencePayload["labels"]>) {
57
+ const values = sorted(observed.flatMap(e => e.payload.labels[label] === null ? [] : [e.payload.labels[label]!]));
58
+ if (values.length > 1) problem("OBSERVATION_CONFLICT");
59
+ else effectiveLabels[label] = values[0] ?? null;
60
+ }
61
+ const states = new Set(observed.map(e => e.payload.state));
62
+ const terminalConflict = states.has("completed") && states.has("failed");
63
+ if (terminalConflict) problem("OBSERVATION_CONFLICT");
64
+ let state: WorkAttempt["state"] = terminalConflict ? "conflicted" : states.has("completed") ? "completed" :
65
+ states.has("failed") ? "failed" : states.has("running") ? "running" : states.has("starting") ? "starting" : "unknown";
66
+ if (identityConflict || badSources.length) {
67
+ state = "conflicted"; parentExecutionId = null; childId = null;
68
+ Object.assign(effectiveLabels, emptyLabels());
69
+ } else {
70
+ const seen = new Set([executionId]);
71
+ let parent = parentExecutionId;
72
+ while (parent !== null) {
73
+ if (seen.has(parent)) { problem("PARENT_CYCLE", parent); break; }
74
+ seen.add(parent);
75
+ const bodies = effective.get(parent);
76
+ if (!bodies?.length) { problem("PARENT_MISSING", parent); break; }
77
+ const alternatives = sorted(bodies.map(e => e.payload.parentExecutionId));
78
+ if (alternatives.length !== 1) { problem("OCCURRENCE_CONFLICT", parent); break; }
79
+ parent = alternatives[0];
80
+ }
81
+ if (effectiveLabels.branchLeafId === null) problem("BRANCH_UNKNOWN");
82
+ }
83
+ const rowProblems = mergeWorkProblems(problems);
84
+ // Support closure can name an occurrence even when its association is not selected. Keep its
85
+ // global identity diagnostics available internally; only selected joins become public attempts.
86
+ for (const candidate of candidates) eventProblems.set(key(eventRef(candidate)), rowProblems);
87
+ if (!bindings.size) continue;
88
+ allProblems.push(...rowProblems);
89
+ occurrences.push(...candidates.map(e => ({ event: eventRef(e), payload: e.payload })));
90
+ attempts.push({ executionId, bindings: sorted(bindings.values()), parentExecutionId, childId, declaredLabels,
91
+ observedLabels, effectiveLabels, state, resolution: rowProblems.length ? "unresolved" : "resolved", problems: rowProblems });
92
+ }
93
+ const runtime: WorkFrozen<WorkRuntime> = { attempts: sorted(attempts), occurrences: sorted(occurrences), counts: {
94
+ attempts: attempts.length,
95
+ variants: new Set(attempts.flatMap(a => a.bindings.flatMap(b => b.variantIds))).size,
96
+ observedCompletedAttempts: attempts.filter(a => a.resolution === "resolved" && a.state === "completed").length,
97
+ } };
98
+ return { runtime, problems: mergeWorkProblems(allProblems), conflicts, eventProblems };
99
+ }
@@ -0,0 +1,262 @@
1
+ import { parseWorkLedgerText } from "./work-ledger.ts";
2
+ import { canonicalWorkJson, freezeWork, workDigest, workResultKey as key, sortWorkResults as unique,
3
+ mergeWorkProblems, mergeWorkConflicts } from "./work-ledger-json.ts";
4
+ import type { EventRef, RevisionRef, WorkFrozen, WorkIngestion, WorkLedgerEvent, WorkProjectionContext,
5
+ WorkClaimResult, WorkObligationResult, WorkProblem, WorkConflict, WorkCoverage, WorkCoverageItem,
6
+ WorkReference, WorkAcceptanceEvent, WorkAvailability } from "./work-ledger-types.ts";
7
+ import type { resolveWorkSnapshotText } from "./work-ledger-snapshot.ts";
8
+ import type { foldWorkOccurrences } from "./work-ledger-occurrences.ts";
9
+
10
+ type IndexedEvent = WorkFrozen<WorkLedgerEvent>;
11
+
12
+ /**
13
+ * Internal first-pass evidence index, NOT the public WorkProjection result.
14
+ * No selector, graph resolution, runtime counts or authority decisions are inferred here.
15
+ * Keep every candidate plus the exact conflicting event references: later reachability must
16
+ * inspect all alternatives, including nonselected associations and redelivered revision bodies.
17
+ */
18
+ interface WorkReplayIndex {
19
+ readonly ingestion: WorkFrozen<WorkIngestion>;
20
+ /** One body per validated (eventId,digest); includes quarantined evidence. */
21
+ readonly candidates: readonly IndexedEvent[];
22
+ /** Every alternative of every ID with multiple digests; not a chosen winner or receipt conflict. */
23
+ readonly conflictingEvents: ReadonlyArray<WorkFrozen<EventRef>>;
24
+ /** Exact revision bodies appearing in those conflicts, independent of delivery ID/timestamp. */
25
+ readonly quarantinedRevisions: ReadonlyArray<WorkFrozen<RevisionRef>>;
26
+ /** Central, frozen source index. Body keys point to IDs, not duplicated Cartesian candidate lists. */
27
+ readonly conflictSources: {
28
+ readonly bodies: Readonly<Record<string, readonly string[]>>;
29
+ readonly events: Readonly<Record<string, readonly WorkFrozen<EventRef>[]>>;
30
+ };
31
+ /** Clean delivery identities only; these are evidence, never proof of acceptance or a current head. */
32
+ readonly effectiveEvents: readonly IndexedEvent[];
33
+ }
34
+
35
+ const revisionIdentity = ({ kind, id, revision, digest }: WorkFrozen<RevisionRef>) => key(["revision", kind, id, revision, digest]);
36
+ /** The ONLY nested identity definition. Claims deliberately have no nested quarantine identity. */
37
+ function nestedIdentity(event: IndexedEvent): string | null {
38
+ switch (event.event) {
39
+ case "work_revision": return revisionIdentity(event.payload.revision);
40
+ case "work_snapshot": return key(["snapshot", event.payload.snapshot.snapshotId, event.payload.snapshot.digest]);
41
+ case "work_occurrence": return key(["occurrence", event.payload]);
42
+ case "work_acceptance": return null;
43
+ }
44
+ }
45
+ type Sources = Pick<WorkReplayIndex, "conflictSources">;
46
+ function sourceIds(index: Sources, event: IndexedEvent | WorkFrozen<EventRef>): readonly string[] {
47
+ const nested = "event" in event ? nestedIdentity(event) : null;
48
+ return [...(index.conflictSources.events[key(event.eventId)] ? [event.eventId] : []),
49
+ ...(nested === null ? [] : index.conflictSources.bodies[nested] ?? [])];
50
+ }
51
+ /** @internal Only index-derived validated events/references. Raw alternatives remain diagnostic input. */
52
+ export function workEventConflictSources(index: Sources, event: IndexedEvent | WorkFrozen<EventRef>): WorkFrozen<EventRef>[] {
53
+ return unique(unique(sourceIds(index, event)).flatMap(id => index.conflictSources.events[key(id)]));
54
+ }
55
+ /** @internal Exact revision references need not have a clean delivery to be quarantined. */
56
+ export function workRevisionConflictSources(index: Sources, ref: WorkFrozen<RevisionRef>): readonly WorkFrozen<EventRef>[] {
57
+ return unique((index.conflictSources.bodies[revisionIdentity(ref)] ?? []).flatMap(id => index.conflictSources.events[key(id)]));
58
+ }
59
+
60
+ function sorted<T>(values: Iterable<T>): T[] {
61
+ // Canonicalize individual rows, not the aggregate. A valid 10,000-record input can have
62
+ // more than 256 candidates/conflicts; the per-wire-array bound must not truncate replay.
63
+ return [...values].map(value => ({ value, key: canonicalWorkJson(value) }))
64
+ .sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0)
65
+ .map(entry => entry.value);
66
+ }
67
+
68
+ /**
69
+ * @internal Strict text -> complete delivery grouping -> quarantine -> effective evidence.
70
+ * Deliberately not re-exported by work-ledger.ts: this is not an unchecked public projector.
71
+ * Raw serialized/archival byte hashes play no role in semantic delivery identity.
72
+ */
73
+ export function indexWorkLedgerText(text: string): WorkReplayIndex {
74
+ const ingestion = parseWorkLedgerText(text);
75
+ const groups = new Map<string, Map<string, IndexedEvent>>();
76
+ for (const event of ingestion.events) {
77
+ let group = groups.get(event.eventId);
78
+ if (!group) { group = new Map(); groups.set(event.eventId, group); }
79
+ if (!group.has(event.digest)) {
80
+ // Equal canonical digests can arrive with different property insertion orders. Normalize
81
+ // the validated body so even serialized index output does not choose an arrival's key order.
82
+ // Parse ONLY our own canonical emission, never raw input. Strict ingestion already checked
83
+ // duplicates, domains and digests. JCS can expand exact exponent tokens beyond the delivered
84
+ // text size, so applying the raw-line byte bound again here would reject valid evidence.
85
+ const normalized = JSON.parse(canonicalWorkJson(event)) as WorkLedgerEvent;
86
+ group.set(event.digest, freezeWork(normalized));
87
+ }
88
+ }
89
+
90
+ const conflictingEvents: EventRef[] = [];
91
+ const quarantined = new Map<string, RevisionRef>(); // Retained diagnostic view, not a second quarantine policy.
92
+ const bodies = new Map<string, Set<string>>(), eventGroups = new Map<string, EventRef[]>();
93
+ for (const [eventId, group] of groups) {
94
+ if (group.size < 2) continue;
95
+ eventGroups.set(key(eventId), sorted([...group.values()].map(event => ({ eventId, digest: event.digest }))));
96
+ for (const event of group.values()) {
97
+ conflictingEvents.push({ eventId, digest: event.digest });
98
+ const identity = nestedIdentity(event);
99
+ if (identity !== null) {
100
+ const ids = bodies.get(identity) ?? new Set<string>();
101
+ ids.add(eventId); bodies.set(identity, ids);
102
+ }
103
+ if (event.event === "work_revision") {
104
+ const { kind, id, revision, digest } = event.payload.revision;
105
+ // Ingestion verified that this digest covers the entire revision, including kind/ID/scope.
106
+ // Quarantine exact bodies, not every fork or successor sharing a stable entity ID.
107
+ quarantined.set(digest, { kind, id, revision, digest });
108
+ }
109
+ }
110
+ }
111
+
112
+ const candidates = sorted([...groups.values()].flatMap(group => [...group.values()]));
113
+ const byKey = <T>(map: Map<string, T>) => [...map].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
114
+ const conflictSources = { bodies: Object.fromEntries(byKey(bodies).map(([identity, ids]) => [identity, [...ids].sort()])),
115
+ events: Object.fromEntries(byKey(eventGroups)) };
116
+ // All existing nested identities use the same source index before any effective evidence is exposed.
117
+ const effectiveEvents = ingestion.complete ? candidates.filter(event => !sourceIds({ conflictSources }, event).length) : [];
118
+ return freezeWork({
119
+ ingestion, candidates, conflictingEvents: sorted(conflictingEvents), conflictSources,
120
+ quarantinedRevisions: sorted(quarantined.values()), effectiveEvents,
121
+ });
122
+ }
123
+
124
+ type Problem = WorkFrozen<WorkProblem>;
125
+ type Claim = WorkFrozen<WorkAcceptanceEvent>;
126
+ const eventRef = (event: IndexedEvent): EventRef => ({ eventId: event.eventId, digest: event.digest });
127
+ const eventReference = (event: IndexedEvent): WorkFrozen<WorkReference> => ({ type: "event", ref: eventRef(event) });
128
+ const isConflict = (p: Problem) => p.code.endsWith("CONFLICT");
129
+ function coverage(items: readonly WorkFrozen<WorkCoverageItem>[]): WorkFrozen<WorkCoverage> {
130
+ const state = (["conflicted", "unavailable", "unknown"] as const).find(s => items.some(item => item.state === s));
131
+ return { state: state ?? (items.length ? "available" : "unknown"), items: unique(items) };
132
+ }
133
+
134
+ /** @internal Global receipt identity diagnostics exist even without a resolved work selector. */
135
+ export function indexWorkReceipts(authority: WorkFrozen<WorkProjectionContext>["authority"]) {
136
+ type Decision = NonNullable<WorkFrozen<WorkProjectionContext>["authority"]>["decisions"][number];
137
+ const receipts = new Map<string, Map<string, Decision>>();
138
+ const conflicts: WorkFrozen<WorkConflict>[] = [];
139
+ for (const d of authority?.decisions ?? []) {
140
+ const group = receipts.get(d.receiptId) ?? new Map<string, Decision>();
141
+ // Complete decision + enclosing authority snapshot; diagnostic identity, NOT authentication.
142
+ group.set(workDigest({ authoritySnapshot: authority!.snapshot, ...d }), d); receipts.set(d.receiptId, group);
143
+ }
144
+ for (const [id, group] of receipts) if (group.size > 1) {
145
+ conflicts.push({ kind: "receipt", id, digests: unique(group.keys()), affectedObligations: [] });
146
+ }
147
+ return { receipts, conflicts: unique(conflicts) };
148
+ }
149
+
150
+ /** @internal Conditional binding proof, not authentication. Only the separately supplied, validated
151
+ * host authority is a TCB input. No wire label, event, receipt-shaped payload or environment is trust. */
152
+ export function projectWorkAcceptance(index: WorkReplayIndex, structure: ReturnType<typeof resolveWorkSnapshotText>,
153
+ authority: WorkFrozen<WorkProjectionContext>["authority"], runtime: ReturnType<typeof foldWorkOccurrences>) {
154
+ const snapshot = structure.snapshot!;
155
+ const claims: WorkFrozen<WorkClaimResult>[] = [], obligations: WorkFrozen<WorkObligationResult>[] = [];
156
+ const problems: Problem[] = [...structure.problems];
157
+ const conflicts: WorkFrozen<WorkConflict>[] = [...structure.conflicts, ...runtime.conflicts];
158
+ const eventConflicts = new Set(index.conflictingEvents.map(e => e.eventId));
159
+ const events = new Map(index.candidates.map(e => [key(eventRef(e)), e]));
160
+ const receiptIndex = indexWorkReceipts(authority), { receipts } = receiptIndex;
161
+ conflicts.push(...receiptIndex.conflicts);
162
+ const decisions = [...receipts.values()].filter(g => g.size === 1).flatMap(g => [...g.values()]);
163
+ function available(kind: "artifact" | "evidence", id: string, digest: string): WorkFrozen<WorkCoverageItem> {
164
+ const values = unique((authority?.availability ?? []).filter(row => row.kind === kind && row.id === id && row.digest === digest).map(row => row.available));
165
+ const state: WorkAvailability = values.length > 1 ? "conflicted" : values.length === 0 ? "unknown" : values[0] ? "available" : "unavailable";
166
+ return { kind, identity: { id, digest }, state };
167
+ }
168
+ const allClaims = index.candidates.filter((e): e is Claim => e.event === "work_acceptance");
169
+ function sourceProblems(ref: WorkFrozen<EventRef>, affected: readonly WorkFrozen<RevisionRef>[]): Problem[] {
170
+ const result: Problem[] = [];
171
+ const found = events.get(key(ref));
172
+ for (const event of workEventConflictSources(index, found ?? ref)) {
173
+ result.push({ code: "EVENT_CONFLICT", reference: { type: "event", ref: event }, affectedObligations: affected });
174
+ }
175
+ if (!found) result.push({ code: "REFERENCE_MISSING", reference: { type: "event", ref }, affectedObligations: affected });
176
+ for (const p of runtime.eventProblems.get(key(ref)) ?? []) result.push({ ...p, affectedObligations: affected });
177
+ return mergeWorkProblems(result);
178
+ }
179
+ for (const row of structure.obligations) {
180
+ const binding = row.binding, affected = [binding.obligation];
181
+ const problem = (code: WorkProblem["code"], reference: WorkFrozen<WorkReference> | null): Problem => ({ code, reference, affectedObligations: affected });
182
+ const inventoryProblem = problem("ARTIFACT_UNSELECTED", { type: "revision", ref: binding.obligation });
183
+ const blockers: Problem[] = [...row.problems, ...(binding.artifact === null ? [inventoryProblem] : [])];
184
+ const selectedClaims = allClaims.filter(e => e.payload.binding.scope.id === snapshot.scope.id && e.payload.binding.obligation.id === binding.obligation.id);
185
+ const applicable = (claim: Claim) => {
186
+ const b = claim.payload.binding;
187
+ return key(b.snapshot) === key(structure.selectedSnapshot) && key(b.scope) === key(snapshot.scope) &&
188
+ key(b.intent) === key(binding.intent) && key(b.obligation) === key(binding.obligation) &&
189
+ key(b.artifact) === key(binding.artifact) && key(b.policy) === key(binding.policy);
190
+ };
191
+ const current = selectedClaims.filter(applicable);
192
+ const currentRefs = new Set(current.map(e => key(eventRef(e))));
193
+ for (const e of current) if (eventConflicts.has(e.eventId)) {
194
+ blockers.push(...index.conflictingEvents.filter(ref => ref.eventId === e.eventId).map(ref => problem("EVENT_CONFLICT", { type: "event", ref })));
195
+ }
196
+ for (const [id, group] of receipts) if (group.size > 1 && [...group.values()].some(d => currentRefs.has(key(d.claim)))) {
197
+ conflicts.push({ kind: "receipt", id, digests: unique(group.keys()), affectedObligations: affected });
198
+ blockers.push(problem("RECEIPT_CONFLICT", { type: "authority", ref: authority!.snapshot }));
199
+ }
200
+ const artifactItems = row.artifact ? [available("artifact", row.artifact.id, row.artifact.contentDigest)] : [];
201
+ const artifactCoverage: WorkFrozen<WorkCoverage> = binding.artifact === null ? { state: "unselected", items: [] } : coverage(artifactItems);
202
+ const evidenceCoverage = coverage(unique(current.flatMap(e => e.payload.binding.evidence.map(ref => available("evidence", ref.id, ref.digest)))));
203
+ function coverageProblems(items: readonly WorkFrozen<WorkCoverageItem>[]): Problem[] {
204
+ return items.filter(item => item.state !== "available").map(item => problem(
205
+ item.state === "conflicted" ? "AVAILABILITY_CONFLICT" : item.state === "unavailable" ? "BYTES_UNAVAILABLE" : "AVAILABILITY_MISSING",
206
+ { type: item.kind === "artifact" ? "artifact-bytes" : "evidence-bytes", ref: item.identity },
207
+ ));
208
+ }
209
+ const rowClaims: WorkFrozen<WorkClaimResult>[] = [];
210
+ const trustedDecisions = new Set<string>();
211
+ for (const claim of selectedClaims) {
212
+ if (eventConflicts.has(claim.eventId)) continue; // No first/last winner row, even for a favorable exact alternative.
213
+ if (!applicable(claim)) {
214
+ rowClaims.push({ claim: eventRef(claim), obligation: claim.payload.binding.obligation, applicability: "superseded",
215
+ matchedReceiptIds: [], problems: [problem("SUPERSEDED_BINDING", eventReference(claim))] });
216
+ continue;
217
+ }
218
+ const b = claim.payload.binding;
219
+ const local: Problem[] = [];
220
+ const bytesMatch = row.artifact !== null && b.artifactDigest === row.artifact.contentDigest;
221
+ if (row.artifact && !bytesMatch) local.push(problem("ARTIFACT_DIGEST_MISMATCH", { type: "artifact-bytes", ref: { id: row.artifact.id, digest: b.artifactDigest } }));
222
+ if (!row.artifact && binding.artifact) local.push(problem("REFERENCE_MISSING", { type: "revision", ref: binding.artifact }));
223
+ const matching = decisions.filter(d => bytesMatch && key(d.claim) === key(eventRef(claim)) &&
224
+ d.authorityId === claim.payload.authorityId && key(d.binding) === key(b));
225
+ for (const d of matching) trustedDecisions.add(d.decision);
226
+ if (!authority) local.push(problem("AUTHORITY_MISSING", null));
227
+ else if (!matching.length) local.push(problem(decisions.some(d => d.claim.eventId === claim.eventId) ? "RECEIPT_MISMATCH" : "RECEIPT_MISSING", eventReference(claim)));
228
+ const support = b.evidence.flatMap(ref => ref.event ? sourceProblems(ref.event, affected) : []);
229
+ const availabilityProblems = coverageProblems([...artifactItems, ...b.evidence.map(ref => available("evidence", ref.id, ref.digest))]);
230
+ local.push(...support, ...availabilityProblems);
231
+ // Only EXACT matching current trusted decisions root global support conflicts. Missing trust,
232
+ // wrong bytes and superseded/mismatched receipts cannot turn an unsupported sibling into a veto.
233
+ if (matching.length) blockers.push(...support.filter(isConflict), ...availabilityProblems.filter(isConflict));
234
+ const rejection = matching.some(d => d.decision === "reject");
235
+ rowClaims.push({ claim: eventRef(claim), obligation: b.obligation,
236
+ applicability: rejection ? "unaccepted" : matching.some(d => d.decision === "accept") && local.length === 0 ? "accepted-under-supplied-authority" : "unresolved",
237
+ matchedReceiptIds: unique(matching.map(d => d.receiptId)),
238
+ problems: rejection ? [problem("TRUSTED_REJECTION", eventReference(claim))] : mergeWorkProblems(local),
239
+ });
240
+ }
241
+ if (trustedDecisions.size > 1) blockers.push(problem("DECISION_CONFLICT", { type: "revision", ref: binding.obligation }));
242
+ const blocked = mergeWorkProblems(blockers);
243
+ for (const p of blocked) if (p.code === "EVENT_CONFLICT" && p.reference?.type === "event") {
244
+ const id = p.reference.ref.eventId;
245
+ conflicts.push({ kind: "event", id, digests: index.conflictingEvents.filter(e => e.eventId === id).map(e => e.digest), affectedObligations: affected });
246
+ }
247
+ const finalClaims = rowClaims.map(c => blocked.length && c.applicability !== "superseded" ? { ...c, applicability: "unresolved" as const, problems: blocked } : c);
248
+ const accepted = finalClaims.some(c => c.applicability === "accepted-under-supplied-authority");
249
+ const rejected = finalClaims.some(c => c.applicability === "unaccepted");
250
+ const unresolved = finalClaims.filter(c => c.applicability === "unresolved");
251
+ const acceptance = blocked.length ? "unresolved" : accepted ? "accepted-under-supplied-authority" : rejected ? "unaccepted" : unresolved.length ? "unresolved" : "unaccepted";
252
+ const obligationProblems = blocked.length ? blocked : accepted ? [] : rejected ?
253
+ finalClaims.filter(c => c.applicability === "unaccepted").flatMap(c => c.problems) : unresolved.length ? unresolved.flatMap(c => c.problems) :
254
+ [problem("NO_CLAIM", { type: "revision", ref: binding.obligation })];
255
+ obligations.push({ binding, acceptance, artifactCoverage, evidenceCoverage, claims: unique(finalClaims.map(c => c.claim)), problems: mergeWorkProblems(obligationProblems) });
256
+ claims.push(...finalClaims); problems.push(...obligationProblems, ...finalClaims.flatMap(c => c.problems));
257
+ }
258
+ return { obligations: unique(obligations), claims: unique(claims), supersededClaims: claims.filter(c => c.applicability === "superseded").length,
259
+ conflicts: mergeWorkConflicts(conflicts), problems: mergeWorkProblems([...problems, ...runtime.problems]),
260
+ progress: { accepted: obligations.filter(o => o.acceptance === "accepted-under-supplied-authority").length, total: obligations.length },
261
+ };
262
+ }