pi-daddy 0.22.0 → 0.24.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 (385) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +12 -6
  3. package/contracts/daily-view/v1/README.md +114 -0
  4. package/contracts/daily-view/v1/fixtures/view.json +525 -0
  5. package/contracts/daily-view/v1/fixtures/view.txt +75 -0
  6. package/contracts/daily-view/v1/fixtures/work.jsonl +17 -0
  7. package/contracts/daily-view/v1/p03/README.md +26 -0
  8. package/contracts/daily-view/v1/p03/fixtures/retained-executions.json +241 -0
  9. package/contracts/daily-view/v1/p03/projection.schema.json +194 -0
  10. package/contracts/daily-view/v1/p03/provenance.json +21 -0
  11. package/contracts/dashboard-host/v1/README.md +176 -0
  12. package/contracts/debrief/v1/README.md +146 -0
  13. package/contracts/debrief/v1/intervention/README.md +32 -0
  14. package/contracts/debrief/v1/intervention/fixtures/artifact-3ee11136beb864f518252e5024dae9197421fbf97b6e981d3243c782b5c44bd0.txt +1 -0
  15. package/contracts/debrief/v1/intervention/fixtures/artifact-58905d872dd3c54a9705f67b05fa82edbad7af6476ae8993faf1a7b9fadfb905.txt +1 -0
  16. package/contracts/debrief/v1/intervention/fixtures/assessment.json +29 -0
  17. package/contracts/debrief/v1/intervention/fixtures/blind-view.json +20 -0
  18. package/contracts/debrief/v1/intervention/fixtures/choice.json +7 -0
  19. package/contracts/debrief/v1/intervention/fixtures/manifest.json +57 -0
  20. package/contracts/debrief/v1/intervention/fixtures/reveal.json +39 -0
  21. package/contracts/debrief/v1/provenance.json +89 -0
  22. package/contracts/debrief/v1/work-capture/README.md +38 -0
  23. package/contracts/debrief/v1/work-capture/fixtures/batch.json +17 -0
  24. package/contracts/debrief/v1/work-capture/fixtures/cases.json +184 -0
  25. package/contracts/debrief/v1/work-capture/fixtures/review.json +184 -0
  26. package/contracts/debrief/v1/work-capture/review-request.schema.json +41 -0
  27. package/contracts/debrief/v1/work-capture/work-case.schema.json +190 -0
  28. package/contracts/debrief/v2/README.md +71 -0
  29. package/contracts/debrief/v2/input-pin.json +66 -0
  30. package/contracts/debrief/v2/intervention-upstream.md +40 -0
  31. package/contracts/debrief/v2/work-signals-upstream.md +22 -0
  32. package/contracts/dispatch-control/v1/README.md +109 -0
  33. package/contracts/dispatch-control/v1/request.schema.json +19 -0
  34. package/contracts/effect-profile/v1/README.md +124 -0
  35. package/contracts/execution-retention/v1/README.md +109 -0
  36. package/contracts/execution-retention/v2/README.md +148 -0
  37. package/contracts/execution-retention/v2/fixtures/interrupted-native-bytes.json +91 -0
  38. package/contracts/execution-retention/v2/fixtures/missing-native-session.json +91 -0
  39. package/contracts/execution-retention/v2/fixtures/native-file-unknown-branch.json +91 -0
  40. package/contracts/execution-retention/v2/fixtures/native-id-without-bytes.json +91 -0
  41. package/contracts/execution-retention/v2/fixtures/native-live-branch.json +90 -0
  42. package/contracts/execution-retention/v2/fixtures/replaced-native-session.json +92 -0
  43. package/contracts/execution-retention/v2/fixtures/result-82c620cdfeb8ca6b858cf653d7f5e2eedca5315d6244216d60e8b36f03223d6a.bin +1 -0
  44. package/contracts/execution-retention/v2/fixtures/session-2ec1f134bc739984434f7fca689e1c3d1c39fce2dcadae709e9b6faedfcc4ac7.bin +3 -0
  45. package/contracts/execution-retention/v2/fixtures/session-9bdb4bd16a901da31580acf056bda525225259e15ec409e0410480109011772f.bin +3 -0
  46. package/contracts/execution-retention/v2/manifest.schema.json +858 -0
  47. package/contracts/experiment/v1/README.md +164 -0
  48. package/contracts/factory-order/v1/README.md +151 -0
  49. package/contracts/factory-order/v1/adoption-pin.json +8 -0
  50. package/contracts/intent-control/v1/README.md +135 -0
  51. package/contracts/intent-control/v1/request.schema.json +1096 -0
  52. package/contracts/intent-control/v2/README.md +34 -0
  53. package/contracts/intent-control/v2/request.schema.json +1070 -0
  54. package/contracts/ledger/v4/README.md +241 -0
  55. package/contracts/ledger/v4/fixtures/layout-options.json +428 -0
  56. package/contracts/ledger/v4/fixtures/work-acceptance.json +57 -0
  57. package/contracts/ledger/v4/fixtures/work-occurrence.json +44 -0
  58. package/contracts/ledger/v4/fixtures/work-revision.json +26 -0
  59. package/contracts/ledger/v4/fixtures/work-snapshot.json +73 -0
  60. package/contracts/ledger/v4/ledger-event.schema.json +162 -0
  61. package/contracts/ordinary-control/v1/README.md +59 -0
  62. package/contracts/producer-ipc/v1/README.md +137 -0
  63. package/dist/check-runner.d.ts +5 -0
  64. package/dist/check-runner.d.ts.map +1 -1
  65. package/dist/check-runner.js +16 -2
  66. package/dist/check-runner.js.map +1 -1
  67. package/dist/cli.d.ts +3 -1
  68. package/dist/cli.d.ts.map +1 -1
  69. package/dist/cli.js +49 -3
  70. package/dist/cli.js.map +1 -1
  71. package/dist/control-journal.d.ts +20 -0
  72. package/dist/control-journal.d.ts.map +1 -0
  73. package/dist/control-journal.js +73 -0
  74. package/dist/control-journal.js.map +1 -0
  75. package/dist/daily-dashboard-host.d.ts +99 -0
  76. package/dist/daily-dashboard-host.d.ts.map +1 -0
  77. package/dist/daily-dashboard-host.js +71 -0
  78. package/dist/daily-dashboard-host.js.map +1 -0
  79. package/dist/daily-view-input.d.ts +44 -0
  80. package/dist/daily-view-input.d.ts.map +1 -0
  81. package/dist/daily-view-input.js +68 -0
  82. package/dist/daily-view-input.js.map +1 -0
  83. package/dist/daily-view-render.d.ts +4 -0
  84. package/dist/daily-view-render.d.ts.map +1 -0
  85. package/dist/daily-view-render.js +41 -0
  86. package/dist/daily-view-render.js.map +1 -0
  87. package/dist/daily-view.d.ts +74 -0
  88. package/dist/daily-view.d.ts.map +1 -0
  89. package/dist/daily-view.js +170 -0
  90. package/dist/daily-view.js.map +1 -0
  91. package/dist/dashboard-cli.d.ts +26 -1
  92. package/dist/dashboard-cli.d.ts.map +1 -1
  93. package/dist/dashboard-cli.js +128 -3
  94. package/dist/dashboard-cli.js.map +1 -1
  95. package/dist/dashboard-harness.d.ts +22 -0
  96. package/dist/dashboard-harness.d.ts.map +1 -0
  97. package/dist/dashboard-harness.js +89 -0
  98. package/dist/dashboard-harness.js.map +1 -0
  99. package/dist/dashboard-herdr.d.ts.map +1 -1
  100. package/dist/dashboard-herdr.js +7 -2
  101. package/dist/dashboard-herdr.js.map +1 -1
  102. package/dist/dashboard-host-contract.d.ts +116 -0
  103. package/dist/dashboard-host-contract.d.ts.map +1 -0
  104. package/dist/dashboard-host-contract.js +14 -0
  105. package/dist/dashboard-host-contract.js.map +1 -0
  106. package/dist/dashboard-host-transport.d.ts +16 -0
  107. package/dist/dashboard-host-transport.d.ts.map +1 -0
  108. package/dist/dashboard-host-transport.js +124 -0
  109. package/dist/dashboard-host-transport.js.map +1 -0
  110. package/dist/dashboard-host.d.ts +203 -0
  111. package/dist/dashboard-host.d.ts.map +1 -0
  112. package/dist/dashboard-host.js +376 -0
  113. package/dist/dashboard-host.js.map +1 -0
  114. package/dist/dashboard-observation.d.ts +28 -0
  115. package/dist/dashboard-observation.d.ts.map +1 -0
  116. package/dist/dashboard-observation.js +86 -0
  117. package/dist/dashboard-observation.js.map +1 -0
  118. package/dist/debrief-contract.d.ts +65 -0
  119. package/dist/debrief-contract.d.ts.map +1 -0
  120. package/dist/debrief-contract.js +151 -0
  121. package/dist/debrief-contract.js.map +1 -0
  122. package/dist/debrief-fixture.d.ts +16 -0
  123. package/dist/debrief-fixture.d.ts.map +1 -0
  124. package/dist/debrief-fixture.js +41 -0
  125. package/dist/debrief-fixture.js.map +1 -0
  126. package/dist/debrief-host.d.ts +59 -0
  127. package/dist/debrief-host.d.ts.map +1 -0
  128. package/dist/debrief-host.js +49 -0
  129. package/dist/debrief-host.js.map +1 -0
  130. package/dist/debrief-render.d.ts +5 -0
  131. package/dist/debrief-render.d.ts.map +1 -0
  132. package/dist/debrief-render.js +65 -0
  133. package/dist/debrief-render.js.map +1 -0
  134. package/dist/debrief.d.ts +90 -0
  135. package/dist/debrief.d.ts.map +1 -0
  136. package/dist/debrief.js +263 -0
  137. package/dist/debrief.js.map +1 -0
  138. package/dist/delegate-types.d.ts +2 -0
  139. package/dist/delegate-types.d.ts.map +1 -1
  140. package/dist/delegate-types.js.map +1 -1
  141. package/dist/delegate.d.ts.map +1 -1
  142. package/dist/delegate.js +1 -0
  143. package/dist/delegate.js.map +1 -1
  144. package/dist/dispatch-control.d.ts +45 -0
  145. package/dist/dispatch-control.d.ts.map +1 -0
  146. package/dist/dispatch-control.js +76 -0
  147. package/dist/dispatch-control.js.map +1 -0
  148. package/dist/effect-profile-runtime.d.ts +40 -0
  149. package/dist/effect-profile-runtime.d.ts.map +1 -0
  150. package/dist/effect-profile-runtime.js +51 -0
  151. package/dist/effect-profile-runtime.js.map +1 -0
  152. package/dist/effect-profile.d.ts +40 -0
  153. package/dist/effect-profile.d.ts.map +1 -0
  154. package/dist/effect-profile.js +152 -0
  155. package/dist/effect-profile.js.map +1 -0
  156. package/dist/execution-retention.d.ts +90 -0
  157. package/dist/execution-retention.d.ts.map +1 -0
  158. package/dist/execution-retention.js +247 -0
  159. package/dist/execution-retention.js.map +1 -0
  160. package/dist/experiment-contract.d.ts +56 -0
  161. package/dist/experiment-contract.d.ts.map +1 -0
  162. package/dist/experiment-contract.js +80 -0
  163. package/dist/experiment-contract.js.map +1 -0
  164. package/dist/experiment-state.d.ts +22 -0
  165. package/dist/experiment-state.d.ts.map +1 -0
  166. package/dist/experiment-state.js +79 -0
  167. package/dist/experiment-state.js.map +1 -0
  168. package/dist/experiment-store.d.ts +30 -0
  169. package/dist/experiment-store.d.ts.map +1 -0
  170. package/dist/experiment-store.js +147 -0
  171. package/dist/experiment-store.js.map +1 -0
  172. package/dist/experiment.d.ts +65 -0
  173. package/dist/experiment.d.ts.map +1 -0
  174. package/dist/experiment.js +357 -0
  175. package/dist/experiment.js.map +1 -0
  176. package/dist/factory-contract.d.ts +79 -0
  177. package/dist/factory-contract.d.ts.map +1 -0
  178. package/dist/factory-contract.js +89 -0
  179. package/dist/factory-contract.js.map +1 -0
  180. package/dist/factory-migration.d.ts +20 -0
  181. package/dist/factory-migration.d.ts.map +1 -0
  182. package/dist/factory-migration.js +55 -0
  183. package/dist/factory-migration.js.map +1 -0
  184. package/dist/factory-order.d.ts +848 -0
  185. package/dist/factory-order.d.ts.map +1 -0
  186. package/dist/factory-order.js +83 -0
  187. package/dist/factory-order.js.map +1 -0
  188. package/dist/factory-registry.d.ts +121 -0
  189. package/dist/factory-registry.d.ts.map +1 -0
  190. package/dist/factory-registry.js +133 -0
  191. package/dist/factory-registry.js.map +1 -0
  192. package/dist/file-lock.d.ts +8 -2
  193. package/dist/file-lock.d.ts.map +1 -1
  194. package/dist/file-lock.js +78 -19
  195. package/dist/file-lock.js.map +1 -1
  196. package/dist/herdr-poll.d.ts +8 -0
  197. package/dist/herdr-poll.d.ts.map +1 -1
  198. package/dist/herdr-poll.js +23 -0
  199. package/dist/herdr-poll.js.map +1 -1
  200. package/dist/index.d.ts +20 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +20 -0
  203. package/dist/index.js.map +1 -1
  204. package/dist/intent-application.d.ts +17 -0
  205. package/dist/intent-application.d.ts.map +1 -0
  206. package/dist/intent-application.js +113 -0
  207. package/dist/intent-application.js.map +1 -0
  208. package/dist/intent-control.d.ts +201 -0
  209. package/dist/intent-control.d.ts.map +1 -0
  210. package/dist/intent-control.js +110 -0
  211. package/dist/intent-control.js.map +1 -0
  212. package/dist/intent-scheduling.d.ts +9 -0
  213. package/dist/intent-scheduling.d.ts.map +1 -0
  214. package/dist/intent-scheduling.js +15 -0
  215. package/dist/intent-scheduling.js.map +1 -0
  216. package/dist/ledger-append.d.ts +15 -0
  217. package/dist/ledger-append.d.ts.map +1 -0
  218. package/dist/ledger-append.js +94 -0
  219. package/dist/ledger-append.js.map +1 -0
  220. package/dist/ledger.d.ts +11 -0
  221. package/dist/ledger.d.ts.map +1 -1
  222. package/dist/ledger.js +3 -7
  223. package/dist/ledger.js.map +1 -1
  224. package/dist/native-session-target.d.ts +9 -0
  225. package/dist/native-session-target.d.ts.map +1 -0
  226. package/dist/native-session-target.js +29 -0
  227. package/dist/native-session-target.js.map +1 -0
  228. package/dist/native-session.d.ts +59 -0
  229. package/dist/native-session.d.ts.map +1 -0
  230. package/dist/native-session.js +154 -0
  231. package/dist/native-session.js.map +1 -0
  232. package/dist/order-schedule.d.ts +37 -0
  233. package/dist/order-schedule.d.ts.map +1 -0
  234. package/dist/order-schedule.js +81 -0
  235. package/dist/order-schedule.js.map +1 -0
  236. package/dist/ordinary-children.d.ts +52 -0
  237. package/dist/ordinary-children.d.ts.map +1 -0
  238. package/dist/ordinary-children.js +93 -0
  239. package/dist/ordinary-children.js.map +1 -0
  240. package/dist/producer-ipc-contract.d.ts +29 -0
  241. package/dist/producer-ipc-contract.d.ts.map +1 -0
  242. package/dist/producer-ipc-contract.js +29 -0
  243. package/dist/producer-ipc-contract.js.map +1 -0
  244. package/dist/producer-ipc.d.ts +55 -0
  245. package/dist/producer-ipc.d.ts.map +1 -0
  246. package/dist/producer-ipc.js +143 -0
  247. package/dist/producer-ipc.js.map +1 -0
  248. package/dist/resource-budget.d.ts +334 -0
  249. package/dist/resource-budget.d.ts.map +1 -0
  250. package/dist/resource-budget.js +415 -0
  251. package/dist/resource-budget.js.map +1 -0
  252. package/dist/retention-contract.d.ts +73 -0
  253. package/dist/retention-contract.d.ts.map +1 -0
  254. package/dist/retention-contract.js +104 -0
  255. package/dist/retention-contract.js.map +1 -0
  256. package/dist/retention-json.d.ts +3 -0
  257. package/dist/retention-json.d.ts.map +1 -0
  258. package/dist/retention-json.js +32 -0
  259. package/dist/retention-json.js.map +1 -0
  260. package/dist/run-child.d.ts +4 -0
  261. package/dist/run-child.d.ts.map +1 -1
  262. package/dist/run-child.js +16 -2
  263. package/dist/run-child.js.map +1 -1
  264. package/dist/run-herdr.d.ts +6 -0
  265. package/dist/run-herdr.d.ts.map +1 -1
  266. package/dist/run-herdr.js +19 -6
  267. package/dist/run-herdr.js.map +1 -1
  268. package/dist/vendor/adoption.d.ts +129 -0
  269. package/dist/vendor/adoption.d.ts.map +1 -0
  270. package/dist/vendor/adoption.js +102 -0
  271. package/dist/vendor/adoption.js.map +1 -0
  272. package/dist/work-command.d.ts +30 -0
  273. package/dist/work-command.d.ts.map +1 -0
  274. package/dist/work-command.js +148 -0
  275. package/dist/work-command.js.map +1 -0
  276. package/dist/work-ledger-destination.d.ts +20 -0
  277. package/dist/work-ledger-destination.d.ts.map +1 -0
  278. package/dist/work-ledger-destination.js +131 -0
  279. package/dist/work-ledger-destination.js.map +1 -0
  280. package/dist/work-ledger-json.d.ts +25 -0
  281. package/dist/work-ledger-json.d.ts.map +1 -0
  282. package/dist/work-ledger-json.js +267 -0
  283. package/dist/work-ledger-json.js.map +1 -0
  284. package/dist/work-ledger-occurrences.d.ts +227 -0
  285. package/dist/work-ledger-occurrences.d.ts.map +1 -0
  286. package/dist/work-ledger-occurrences.js +115 -0
  287. package/dist/work-ledger-occurrences.js.map +1 -0
  288. package/dist/work-ledger-projection.d.ts +295 -0
  289. package/dist/work-ledger-projection.d.ts.map +1 -0
  290. package/dist/work-ledger-projection.js +237 -0
  291. package/dist/work-ledger-projection.js.map +1 -0
  292. package/dist/work-ledger-snapshot.d.ts +29 -0
  293. package/dist/work-ledger-snapshot.d.ts.map +1 -0
  294. package/dist/work-ledger-snapshot.js +267 -0
  295. package/dist/work-ledger-snapshot.js.map +1 -0
  296. package/dist/work-ledger-types.d.ts +261 -0
  297. package/dist/work-ledger-types.d.ts.map +1 -0
  298. package/dist/work-ledger-types.js +13 -0
  299. package/dist/work-ledger-types.js.map +1 -0
  300. package/dist/work-ledger-validation.d.ts +14 -0
  301. package/dist/work-ledger-validation.d.ts.map +1 -0
  302. package/dist/work-ledger-validation.js +226 -0
  303. package/dist/work-ledger-validation.js.map +1 -0
  304. package/dist/work-ledger.d.ts +43 -0
  305. package/dist/work-ledger.d.ts.map +1 -0
  306. package/dist/work-ledger.js +225 -0
  307. package/dist/work-ledger.js.map +1 -0
  308. package/extensions/daily-dashboard-session.ts +30 -0
  309. package/extensions/daily-work-session.ts +20 -0
  310. package/extensions/delegate-chain.ts +1 -0
  311. package/extensions/delegation-native.ts +12 -0
  312. package/extensions/delegation.ts +25 -11
  313. package/extensions/execute-child.ts +44 -5
  314. package/extensions/executor-session.ts +9 -0
  315. package/extensions/grants-command.ts +14 -15
  316. package/extensions/grants-connected-command.ts +19 -0
  317. package/extensions/grants.ts +26 -2
  318. package/extensions/ordinary-runtime.ts +14 -0
  319. package/extensions/primary-shadow.ts +11 -0
  320. package/extensions/run-delegation.ts +9 -7
  321. package/extensions/session.ts +10 -20
  322. package/extensions/work-runtime.ts +44 -0
  323. package/package.json +78 -4
  324. package/src/check-runner.ts +20 -2
  325. package/src/cli.ts +41 -4
  326. package/src/control-journal.ts +26 -0
  327. package/src/daily-dashboard-host.ts +49 -0
  328. package/src/daily-view-input.ts +59 -0
  329. package/src/daily-view-render.ts +44 -0
  330. package/src/daily-view.ts +152 -0
  331. package/src/dashboard-cli.ts +108 -4
  332. package/src/dashboard-harness.ts +37 -0
  333. package/src/dashboard-herdr.ts +7 -2
  334. package/src/dashboard-host-contract.ts +45 -0
  335. package/src/dashboard-host-transport.ts +40 -0
  336. package/src/dashboard-host.ts +191 -0
  337. package/src/dashboard-observation.ts +45 -0
  338. package/src/debrief-contract.ts +127 -0
  339. package/src/debrief-fixture.ts +29 -0
  340. package/src/debrief-host.ts +53 -0
  341. package/src/debrief-render.ts +49 -0
  342. package/src/debrief.ts +198 -0
  343. package/src/delegate-types.ts +2 -0
  344. package/src/delegate.ts +1 -0
  345. package/src/dispatch-control.ts +81 -0
  346. package/src/effect-profile-runtime.ts +46 -0
  347. package/src/effect-profile.ts +129 -0
  348. package/src/execution-retention.ts +230 -0
  349. package/src/experiment-contract.ts +66 -0
  350. package/src/experiment-state.ts +44 -0
  351. package/src/experiment-store.ts +98 -0
  352. package/src/experiment.ts +231 -0
  353. package/src/factory-contract.ts +47 -0
  354. package/src/factory-migration.ts +29 -0
  355. package/src/factory-order.ts +56 -0
  356. package/src/factory-registry.ts +60 -0
  357. package/src/file-lock.ts +47 -11
  358. package/src/herdr-poll.ts +18 -0
  359. package/src/index.ts +32 -0
  360. package/src/intent-application.ts +90 -0
  361. package/src/intent-control.ts +107 -0
  362. package/src/intent-scheduling.ts +14 -0
  363. package/src/ledger-append.ts +72 -0
  364. package/src/ledger.ts +3 -9
  365. package/src/native-session-target.ts +23 -0
  366. package/src/native-session.ts +139 -0
  367. package/src/order-schedule.ts +42 -0
  368. package/src/ordinary-children.ts +59 -0
  369. package/src/producer-ipc-contract.ts +26 -0
  370. package/src/producer-ipc.ts +107 -0
  371. package/src/resource-budget.ts +349 -0
  372. package/src/retention-contract.ts +84 -0
  373. package/src/retention-json.ts +20 -0
  374. package/src/run-child.ts +13 -2
  375. package/src/run-herdr.ts +11 -7
  376. package/src/vendor/adoption.ts +109 -0
  377. package/src/work-command.ts +179 -0
  378. package/src/work-ledger-destination.ts +113 -0
  379. package/src/work-ledger-json.ts +228 -0
  380. package/src/work-ledger-occurrences.ts +99 -0
  381. package/src/work-ledger-projection.ts +262 -0
  382. package/src/work-ledger-snapshot.ts +246 -0
  383. package/src/work-ledger-types.ts +229 -0
  384. package/src/work-ledger-validation.ts +174 -0
  385. package/src/work-ledger.ts +186 -0
@@ -0,0 +1,191 @@
1
+ import { isOrdinaryChildren, holdOrdinaryDispatch, type OrdinaryChildren, type OrdinaryCancellation } from "./ordinary-children.ts";
2
+ import { loadedDashboardHarnessDigest } from "./dashboard-harness.ts";
3
+ import { join, isAbsolute } from "node:path";
4
+ import { dataDigest, detached, freeze, sha, reviewPage } from "./debrief-contract.ts";
5
+ import { createRetainedDebrief } from "./debrief-host.ts";
6
+ import type { DebriefCheckpoint, DebriefPresenter, DebriefPersistence } from "./debrief.ts";
7
+ import { debriefAction } from "./debrief-render.ts";
8
+ import { openResourceBudget, resourceBindingDigest, type GovernedBudgetBinding } from "./resource-budget.ts";
9
+ import { dispatchRequest, dispatchRequestDigest, controlShape, type DispatchRequest } from "./dispatch-control.ts";
10
+ import { intentRequest, intentRequestDigest, type IntentRequest } from "./intent-control.ts";
11
+ import { experimentBindingDigest, isExperimentController, type ExperimentCancellation, type openExperiment } from "./experiment.ts";
12
+ import { dashboardObservations } from "./dashboard-observation.ts";
13
+ import { dashboardHostDigest, dashboardHostRequest, dashboardHostRequestDigest, dashboardSelectionDigest,
14
+ type DashboardHarness, type DashboardHostConfig, type DashboardHostRequest, type DashboardHostAuthority } from "./dashboard-host-contract.ts";
15
+ export * from "./dashboard-host-contract.ts";
16
+ const hosts=new WeakSet<object>();
17
+ export const isDashboardHost=(value:unknown):value is DashboardHost=>typeof value==="object"&&value!==null&&hosts.has(value);
18
+ export type DashboardHost=ReturnType<typeof openDashboardHost>;
19
+ export interface DashboardHostOptions { ordinary?:OrdinaryChildren; harness:DashboardHarness; config:DashboardHostConfig; budget:GovernedBudgetBinding; experiment?:ReturnType<typeof openExperiment>; authority:()=>DashboardHostAuthority|null;
20
+ /** Exact actions constructed and authorized by the owner; the dashboard exposes only human labels/keys. */
21
+ humanActions?:(context:{hostDigest:string;selectionDigest:string;tip:string;observations:readonly {sourceId:string;checkpointId:string}[]})=>readonly {key:string;label:string;request:DashboardHostRequest}[]|Promise<readonly {key:string;label:string;request:DashboardHostRequest}[]>;
22
+ /** Explicit approved observation effect only; refresh/frame never invokes it. */
23
+ beforeObservation?:(sourceId:string)=>Promise<void>;
24
+ /** Independently sourced current declaration. Never inferred from idle, a PID, title or request fields. */
25
+ presence?:()=>{present:boolean;closing:boolean;evidenceDigest:string;expiresAt:number}|null;
26
+ }
27
+ function configuration(input:DashboardHostConfig){
28
+ const c=detached(input);controlShape(c,["version","trustDirectory","trustPolicyId","archiveRoot","scope","author","policyPath","policySha256","sources","selection","cases","blind","budgetDigest","experimentDigest","harnessArtifactDigest",...(Object.hasOwn(c,"ordinaryDigest")?["ordinaryDigest"]:[])]);
29
+ if(Object.hasOwn(c,"ordinaryDigest")&&!sha(c.ordinaryDigest))throw Error("invalid ordinary binding digest");
30
+ if(c.version!=="producer-dashboard-host-v1"||![c.trustDirectory,c.archiveRoot,c.policyPath].every(p=>typeof p==="string"&&isAbsolute(p))||![c.trustPolicyId,c.policySha256,c.budgetDigest,c.harnessArtifactDigest].every(sha)||!(c.experimentDigest===null||sha(c.experimentDigest))||typeof c.scope!=="string"||!c.scope||c.scope.length>128||typeof c.author!=="string"||!c.author||c.author.length>256||!Array.isArray(c.sources)||c.sources.length<1||c.sources.length>32||new Set(c.sources.map(s=>s.id)).size!==c.sources.length||c.sources.filter(s=>s.kind==="work").length>1||c.sources.some(s=>!/^[a-zA-Z0-9:_-]{1,128}$/.test(s.id)||!["work","retention","facts"].includes(s.kind)))throw Error("invalid explicit dashboard host binding");
31
+ return freeze(c);
32
+ }
33
+ /** Explicit creation, NEVER called by refresh/reconnect. One fixed host child per registered trust store. */
34
+ export function createDashboardHost(options:DashboardHostOptions){
35
+ const c=configuration(options.config),h=options.harness,a=options.authority();
36
+ if(loadedDashboardHarnessDigest(h)!==c.harnessArtifactDigest)throw Error("verified loaded harness artifact required");
37
+ if(!a?.hostDigests.includes(dashboardHostDigest(c)))throw Error("independent exact host authority required");
38
+ const trust=h.openTrustLifecycle(c.trustDirectory),view=trust.inspect(Date.now());
39
+ if(view.policyId!==c.trustPolicyId)throw Error("trust policy changed");
40
+ h.learningJournal(join(c.trustDirectory,"producer-host"),{type:"producer-host-initial",config:c});
41
+ return openDashboardHost(options);
42
+ }
43
+ /** Existing native writers + original controllers; no model, launch, authority-file loader or PID recovery. */
44
+ export function openDashboardHost(options:DashboardHostOptions){
45
+ const c=configuration(options.config),h=options.harness,hostDigest=dashboardHostDigest(c),selectionDigest=dashboardSelectionDigest(c.selection);
46
+ if(loadedDashboardHarnessDigest(h)!==c.harnessArtifactDigest)throw Error("verified loaded harness artifact required");
47
+ const budget=openResourceBudget(options.budget),experiment=options.experiment,ordinary=options.ordinary;
48
+ if(ordinary&&(!isOrdinaryChildren(ordinary)||ordinary.bindingDigest!==c.ordinaryDigest))throw Error("original ordinary controller binding required");
49
+ if(resourceBindingDigest(budget.binding)!==c.budgetDigest||experiment&&(!isExperimentController(experiment)||experimentBindingDigest(experiment.binding)!==c.experimentDigest))throw Error("original controller binding required");
50
+ const trust=h.openTrustLifecycle(c.trustDirectory),journal=h.learningJournal(join(c.trustDirectory,"producer-host"));
51
+ const initial=journal.read()[0].value;
52
+ if(initial.type!=="producer-host-initial"||dataDigest(initial.config)!==hostDigest)throw Error("host store binding changed; no new budget");
53
+ const trustInput=trust.history()[0].value.input as {archiveRoot:string;component:unknown;exposure:{attentionRemaining:number}|null};
54
+ if(trustInput.archiveRoot!==c.archiveRoot||trust.inspect(Date.now()).policyId!==c.trustPolicyId||trustInput.exposure&&trustInput.exposure.attentionRemaining>5)throw Error("one scoped five-total attention policy required");
55
+ const authority=()=>{const a=options.authority();if(!a)return null;const safe=detached(a);if(!safe.hostDigests.includes(hostDigest))return null;return safe;};
56
+ const observation=dashboardObservations(h,c,journal.read),history=()=>journal.read();
57
+ const append=(value:Record<string,unknown>)=>journal.append(history().at(-1)!.id,value);
58
+ const saved=():DebriefCheckpoint|null=>{const rows=history().filter(e=>e.value.type==="checkpoint");return rows.length?detached(rows.at(-1)!.value.checkpoint) as DebriefCheckpoint:null;};
59
+ let presenter:DebriefPresenter|undefined,busy=false,poisoned=false,presentationRevision:number|null=null,presenceDigest:string|null=null,attentionDeferred:string|null=null;
60
+ const ordinaryDispatchHoldKey=dataDigest({hostDigest,control:"pause-new-ordinary-dispatch"});
61
+ let displayedActions:null|{tip:string;actions:{key:string;label:string;operation:DashboardHostRequest["operation"];request:DashboardHostRequest}[];digest:string}=null;
62
+ const presence=()=>{const p=options.presence?.();return p&&p.present===true&&p.closing===true&&sha(p.evidenceDigest)&&Number.isFinite(p.expiresAt)&&p.expiresAt>Date.now()?p:null;};
63
+ const persistence:DebriefPersistence={durability:"host-owned",load:saved,compareAndSwap(expected,next){
64
+ const rows=history(),previous=saved();if((previous?dataDigest(previous):null)!==expected)throw Error("stale attention CAS");
65
+ if(previous&&(dataDigest(previous.caseIds)!==dataDigest(next.caseIds)||previous.blindDigest!==next.blindDigest))throw Error("no replacement cards or budget refill");
66
+ const cards=[...next.caseIds,...(next.blindDigest?[next.blindDigest]:[])];if(cards.length>5)throw Error("five total cards");
67
+ if(!previous)for(const card of cards){const result=trust.expose("producer:"+dataDigest({hostDigest,card}),Date.now());if(result.mode!=="ask"){attentionDeferred=result.reason;throw Error("predeclared policy defers this question: "+result.reason);}}
68
+ journal.append(rows.at(-1)!.id,{type:"checkpoint",checkpoint:next});
69
+ }};
70
+ const boundary=async()=>{const dispatch=await budget.controls(null).inspect(),accounting=await budget.inspect();return {dispatch,active:accounting.active};};
71
+ const paused=async(revision:number)=>{const b=await boundary();if(ordinary&&!ordinary.quiescent())throw Error("original ordinary children not quiescent");if(experiment){const view=await experiment.inspect();if(view.budget?.active!==0||view.control!=="not-assessed")throw Error("original experiment not quiescent");}if(!b.dispatch.paused||b.dispatch.admission!=="paused"||b.dispatch.revision!==revision||b.active!==0)throw Error("safe paused selection unavailable");};
72
+ const currentSelection=async()=>budget.binding.intent?(await budget.intentControls(null).inspect()).selection:c.selection;
73
+ const nativeRead=async()=>({dispatch:await budget.controls(null).inspect(),intent:budget.binding.intent?await budget.intentControls(null).inspect():null,experiment:experiment?await experiment.inspect():null,ordinary:ordinary?ordinary.inspect():null});
74
+ const availableHumanActions=async()=>{
75
+ const rows=history(),tip=rows.at(-1)!.id,selection=await currentSelection(),seen=new Set<string>();
76
+ const declared=await options.humanActions?.({hostDigest,selectionDigest:dashboardSelectionDigest(selection),tip,observations:observation.latest().map(x=>({sourceId:x.sourceId,checkpointId:x.checkpointId}))})??[],a=authority();
77
+ return declared.map(value=>{const x=detached(value);controlShape(x,["key","label","request"]);const request=dashboardHostRequest(x.request);
78
+ if(!/^[a-zA-Z0-9:_-]{1,64}$/.test(x.key)||typeof x.label!=="string"||!x.label.trim()||Buffer.byteLength(x.label)>120||seen.has(x.key))throw Error("invalid human dashboard action");seen.add(x.key);
79
+ if(request.hostDigest!==hostDigest||request.expectedTip!==tip||request.selectionDigest!==dashboardSelectionDigest(selection)||!a?.requestDigests.includes(dashboardHostRequestDigest(request)))throw Error("human dashboard action is stale or not exactly authorized");
80
+ return {key:x.key,label:x.label,operation:request.operation,request};});
81
+ };
82
+ const api={hostDigest,selectionDigest,
83
+ async frame(){
84
+ const rows=history(),tip=rows.at(-1)!.id,a=authority();let source:unknown=null,error:string|null=null;
85
+ let selection=c.selection,selectionState="current-control-snapshot";
86
+ try{selection=await currentSelection();}catch(e){error=String(e);selectionState="unavailable-host-declaration-only";}
87
+ try{source=await observation.view(selectionState==="current-control-snapshot"?a:null,selection);}catch(e){error=String(e);}
88
+ let debrief:unknown=null;
89
+ if(a&&presenter&&presentationRevision!==null&&presence()?.evidenceDigest===presenceDigest){try{await paused(presentationRevision);debrief=presenter.view();}catch{debrief=null;}}
90
+ let controls:unknown=null;try{controls=await nativeRead();}catch(e){error=String(e);}
91
+ const attention=trust.inspect(Date.now());let actions:{key:string;label:string;operation:string}[]=[];
92
+ try{const available=await availableHumanActions(),digest=dataDigest(available.map(({key,label,operation,request})=>({key,label,operation,requestDigest:dashboardHostRequestDigest(request)})));
93
+ if(displayedActions?.tip===tip&&displayedActions.digest!==digest)throw Error("displayed dashboard actions changed before journal advance");
94
+ displayedActions={tip,actions:available,digest};actions=available.map(({key,label,operation})=>({key,label,operation}));}catch(e){error=String(e);}
95
+ return freeze(detached({version:"producer-dashboard-frame-v1",hostDigest,selectionDigest:dashboardSelectionDigest(selection),selectionState,tip,source,controls,debrief,attention,error,actions,
96
+ requests:rows.filter(e=>["claim","result","presentation","defer","ordinary-intent-pending"].includes(String(e.value.type))).map(e=>e.value),
97
+ control:rows.some(e=>e.value.type==="host-failure")?"failed":rows.some(e=>e.value.type==="claim"&&!rows.some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId))?"unknown":"not-assessed",
98
+ acknowledgement:poisoned?"unknown":"readback-only",identity:"independently-declared-host; not human/module authentication",activeBranch:null,acceptance:"not-assessed",freshness:"snapshot-unknown",workerInteractions:0}));
99
+ },
100
+ /** Explicit reconciliation is read-only. A retained claim is never replayed as an effect. */
101
+ reconcile:()=>api.frame(),
102
+ async humanAction(key:string){const tip=history().at(-1)!.id;if(!displayedActions||displayedActions.tip!==tip)throw Error("dashboard actions must be displayed at the current tip");
103
+ const found=displayedActions.actions.find(action=>action.key===key);if(!found)throw Error(`unknown dashboard action ${JSON.stringify(key)}`);
104
+ const current=(await availableHumanActions()).find(action=>action.key===key);if(!current||current.label!==found.label||dashboardHostRequestDigest(current.request)!==dashboardHostRequestDigest(found.request))throw Error("displayed dashboard action changed; no effect attempted");return api.action(found.request);},
105
+ async action(input:DashboardHostRequest){
106
+ const request=dashboardHostRequest(input),digest=dashboardHostRequestDigest(request);
107
+ if(busy||poisoned)throw Error("dashboard operation busy or acknowledgement unknown");busy=true;let attempted=false;
108
+ try{
109
+ const rows=history(),old=rows.find(e=>e.value.type==="claim"&&e.value.requestId===request.requestId);
110
+ if(old){if(old.value.digest!==digest)throw Error("immutable dashboard request ID");return {state:rows.some(e=>e.value.type==="host-failure")?"failed-or-unknown":"readback-only",result:rows.find(e=>e.value.type==="result"&&e.value.requestId===request.requestId)?.value??null};}
111
+ const selection=request.operation==="observe"?await currentSelection().catch(()=>c.selection):await currentSelection();
112
+ if(request.hostDigest!==hostDigest||request.selectionDigest!==dashboardSelectionDigest(selection)||request.expectedTip!==rows.at(-1)!.id)throw Error("stale dashboard selection/CAS");
113
+ if(rows.some(e=>e.value.type==="host-failure")||rows.some(e=>e.value.type==="claim"&&!rows.some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId)))throw Error("prior acknowledgement unknown; inspect without repeating effects");
114
+ const a=authority(),approved=Boolean(a?.requestDigests.includes(digest));
115
+ attempted=true;journal.append(request.expectedTip,{type:"claim",requestId:request.requestId,digest,request,decision:approved?"approved":"denied"});
116
+ if(!approved){append({type:"result",requestId:request.requestId,state:"denied"});return {state:"denied"};}
117
+ let result:unknown,releaseOrdinary:(()=>void)|undefined;
118
+ try{
119
+ if(request.operation==="observe"){
120
+ const sourceId=(request.payload as {sourceId?:unknown})?.sourceId;if(typeof sourceId!=="string")throw Error("exact observation source required");await options.beforeObservation?.(sourceId);
121
+ const captured=observation.observe(request.payload,a!,selection);append({type:"observation",observation:captured});result=captured;
122
+ }else if(request.operation==="present"){
123
+ const p=detached(request.payload) as {userPresent:boolean;closing:boolean;evidenceDigest:string;dispatchRevision:number};
124
+ if(Object.keys(p).sort().join()!=="closing,dispatchRevision,evidenceDigest,userPresent"||!sha(p.evidenceDigest)||typeof p.userPresent!=="boolean"||typeof p.closing!=="boolean"||!Number.isSafeInteger(p.dispatchRevision))throw Error("explicit presence/boundary evidence required");
125
+ if(!p.userPresent||!p.closing||presence()?.evidenceDigest!==p.evidenceDigest){append({type:"defer",requestId:request.requestId,evidence:p,reason:"absent or uncertain closing boundary",weekly:true});presenter?.close();presentationRevision=null;result={state:"deferred"};}
126
+ else{
127
+ await paused(p.dispatchRevision);const cases=observation.latest().find(r=>r.kind==="work")?.cases??c.cases;if(!cases)throw Error("retained selected case batch unavailable");
128
+ const reviewer=cases.version==="work-signals-v1"?h.createWorkSignalReviewer(c.archiveRoot,cases.batchId,c.author):h.createWorkCaseReviewer(c.archiveRoot,cases.batchId,c.author);
129
+ const page=reviewPage(await reviewer.list(0,c.blind?4:5),c.blind?4:5,0,cases.version==="work-signals-v1"?{batchId:cases.batchId,observationId:cases.observationId}:undefined);
130
+ if(page.items.some(i=>dataDigest({kind:"detector",...i.candidate.detector as object})!==dataDigest(trustInput.component)))throw Error("case population/version differs from predeclared trust policy");
131
+ presenter=createRetainedDebrief(h,{archiveRoot:c.archiveRoot,scope:c.scope,author:c.author,cases,blind:c.blind??undefined,persistence});
132
+ attentionDeferred=null;const view=await presenter.open({mode:"manual",userPresent:true});await paused(p.dispatchRevision);
133
+ if(attentionDeferred){presenter.close();presentationRevision=null;append({type:"defer",requestId:request.requestId,reason:attentionDeferred,weekly:true});result={state:"deferred",reason:attentionDeferred};}
134
+ else {if(view.state!=="open")throw Error("presentation data/attention acknowledgement unavailable");
135
+ presentationRevision=p.dispatchRevision;presenceDigest=p.evidenceDigest;const frameDigest=dataDigest(view);append({type:"presentation",requestId:request.requestId,evidence:p,frameDigest,state:"prepared-not-delivery",automatic:"unqualified"});result={state:"prepared",frameDigest};}
136
+ }
137
+ }else if(request.operation==="presented"){
138
+ const p=detached(request.payload) as {frameDigest:string};if(Object.keys(p).join()!=="frameDigest"||!presenter||presentationRevision===null)throw Error("original prepared presentation required");
139
+ await paused(presentationRevision);if(presence()?.evidenceDigest!==presenceDigest)throw Error("user absent/presence changed");if(p.frameDigest!==dataDigest(presenter.view()))throw Error("stale presentation acknowledgement");result={state:"host-acknowledged-presentation",frameDigest:p.frameDigest,humanAuthentication:"unqualified"};
140
+ }else if(request.operation==="defer"){
141
+ presenter?.close();presentationRevision=null;append({type:"defer",requestId:request.requestId,weekly:true,evidence:request.payload});result={state:"deferred"};
142
+ }else if(request.operation==="debrief"){
143
+ if(!presenter||presentationRevision===null||typeof request.payload!=="string")throw Error("original paused presenter required");await paused(presentationRevision);if(presence()?.evidenceDigest!==presenceDigest)throw Error("user absent/presence changed");result=await debriefAction(presenter,request.payload);await paused(presentationRevision);if(result==="unknown"||result==="stale-or-unknown")throw Error("debrief writer acknowledgement unknown");
144
+ }else{
145
+ presenter?.close();presentationRevision=null;
146
+ if(request.operation==="dispatch"||request.operation==="dispatch-reconcile"){
147
+ const native=dispatchRequest(request.payload as DispatchRequest);if(native.action==="cancel-execution")throw Error("cancellation needs exact separately approved native bridge");
148
+ const port=budget.controls(a!.dispatch),ordinaryHold=ordinary&&["pause-dispatch","resume-dispatch"].includes(native.action)?holdOrdinaryDispatch(ordinary,ordinaryDispatchHoldKey):undefined;
149
+ if(request.operation==="dispatch-reconcile"&&(await port.inspect()).records.find(r=>r.request.requestId===native.requestId)?.digest!==dispatchRequestDigest(native))throw Error("exact original dispatch reconciliation required");
150
+ result=request.operation==="dispatch"?await port.request(native):await port.reconcile(native.requestId);
151
+ const record=(result as {records:{request:{requestId:string};decision:string;application:string}[]}).records.find(r=>r.request.requestId===native.requestId);
152
+ if(ordinaryHold&&(record?.decision!=="approved"||native.action==="resume-dispatch"&&record.application==="applied"))ordinaryHold.release();
153
+ }else if(request.operation==="intent"||request.operation==="intent-reconcile"){
154
+ const native=detached(intentRequest(request.payload as IntentRequest)) as IntentRequest,port=budget.intentControls(a!.dispatch),digest=intentRequestDigest(native);
155
+ if(c.ordinaryDigest&&!ordinary)throw Error("original ordinary boundary unavailable; no recovery");
156
+ if(!ordinary)result=request.operation==="intent"?await port.request(native):await port.reconcile(native);
157
+ else {
158
+ const pending=history().filter(e=>e.value.type==="ordinary-intent-pending"&&e.value.nativeRequestId===native.requestId);
159
+ if(pending.some(e=>e.value.digest!==digest))throw Error("immutable pending ordinary intent ID");
160
+ if(pending.some(e=>!history().some(row=>{const original=row.value.request as DashboardHostRequest|undefined;return row.value.type==="claim"&&row.value.requestId===e.value.hostRequestId&&row.value.decision==="approved"&&original&&["intent","intent-reconcile"].includes(original.operation)&&intentRequestDigest(original.payload as IntentRequest)===digest;})))throw Error("unbound ordinary pending transport");
161
+ const exists=(await port.inspect()).records.some(r=>r.requestId===native.requestId);
162
+ if(request.operation==="intent-reconcile"&&!exists&&!pending.length)throw Error("original pending intent required for reconciliation");
163
+ if(ordinary&&(a!.dispatch?.authorityDigest!==budget.binding.authorityDigest||!a!.dispatch.requestDigests.includes(digest)))throw Error("independent native intent authority required before ordinary hold");
164
+ const held=ordinary?holdOrdinaryDispatch(ordinary,dataDigest({hostDigest,digest})):undefined;
165
+ if(held&&!held.ready()){
166
+ if(!pending.length)append({type:"ordinary-intent-pending",nativeRequestId:native.requestId,digest,hostRequestId:request.requestId});
167
+ result={state:"pending-ordinary-boundary",nativeRequestId:native.requestId,application:"not-acknowledged"};
168
+ }else{
169
+ result=(request.operation==="intent"||pending.length>0&&!exists)?await port.request(native):await port.reconcile(native);
170
+ const outcome=(result as {records:{requestId:string;application:string}[]}).records.find(r=>r.requestId===native.requestId);
171
+ if(held&&!outcome)throw Error("native intent application acknowledgement unavailable");
172
+ if(held&&["applied","not-applied"].includes(outcome!.application))releaseOrdinary=held.release;
173
+ }
174
+ }
175
+ }else if(request.operation==="ordinary-cancel"){
176
+ if(!ordinary||!isOrdinaryChildren(ordinary))throw Error("original ordinary child handles unavailable; no recovery");
177
+ result=ordinary.cancel(request.payload as OrdinaryCancellation,a!.ordinary??null);
178
+ }else if(request.operation==="cancel"){
179
+ const p=detached(request.payload) as {dispatch:DispatchRequest;cancellation:ExperimentCancellation};controlShape(p,["dispatch","cancellation"]);const legacy=dispatchRequest(p.dispatch);
180
+ const current=await budget.controls(null).inspect();
181
+ if(!experiment||!isExperimentController(experiment)||legacy.action!=="cancel-execution"||legacy.bindingDigest!==c.budgetDigest||legacy.expectedRevision!==current.revision||legacy.targetExecutionId!==p.cancellation.executionId||legacy.requestId!==p.cancellation.requestId)throw Error("exact original cancellation target/revision required");
182
+ if(a!.dispatch?.authorityDigest!==budget.binding.authorityDigest||!a!.dispatch.requestDigests.includes(dispatchRequestDigest(legacy)))throw Error("independent legacy cancellation authority required");
183
+ result=await experiment.cancel(p.cancellation,a!.experiment);
184
+ }else throw Error("unsupported dashboard operation");
185
+ }
186
+ }catch(error){presenter?.close();presentationRevision=null;append({type:"result",requestId:request.requestId,state:"failed-or-unknown",reason:String(error)});throw error;}
187
+ append({type:"result",requestId:request.requestId,state:"acknowledged",resultDigest:dataDigest(result??null)});releaseOrdinary?.();return {state:"acknowledged",result};
188
+ }catch(error){if(attempted){poisoned=true;try{if(history().some(e=>e.value.type==="claim"&&e.value.requestId===request.requestId))append({type:"host-failure",requestId:request.requestId,reason:String(error)});}catch{/* Failure recording also unacknowledged; no claim of no effects. */}}throw error;}finally{busy=false;}
189
+ }
190
+ };hosts.add(api);return Object.freeze(api);
191
+ }
@@ -0,0 +1,45 @@
1
+ import { parseRetentionJson } from "./retention-json.ts";
2
+ import { createHash } from "node:crypto";
3
+ import { createDailyViewReader } from "./daily-view.ts";
4
+ import { dataDigest, detached } from "./debrief-contract.ts";
5
+ import type { DashboardHarness, DashboardHostConfig, DashboardHostAuthority, HostEvent } from "./dashboard-host-contract.ts";
6
+ export interface DashboardObservation { sourceId: string; kind: "work" | "retention" | "facts"; checkpointId: string; sourceManifestId: string | null; metadata: unknown; cases: { version: "work-signals-v1"; batchId: string; observationId: string } | null; linkageManifestId: string | null }
7
+ export function dashboardObservations(h: DashboardHarness, c: DashboardHostConfig, history: () => HostEvent[]) {
8
+ const reader=createDailyViewReader(), root=c.archiveRoot;
9
+ const latest=()=>{const rows=new Map<string,DashboardObservation>();for(const e of history())if(e.value.type==="observation") {const row=e.value.observation as unknown as DashboardObservation;rows.set(row.sourceId,row);}return [...rows.values()];};
10
+ const policy=(id:string)=>{const binding=h.archivePolicyBinding(c.policyPath,id);if(binding.archiveRoot!==root||binding.policySha256!==c.policySha256)throw Error("source policy binding changed");};
11
+ const bytes=(id:string)=>{const source=h.readArchiveSource(root,id);if(source.status!=="available"||source.reference?.retention!=="exact"||!source.bytes||createHash("sha256").update(source.bytes).digest("hex")!==source.reference.sha256)throw Error("exact retained source unavailable");return Uint8Array.from(source.bytes);};
12
+ return { latest,
13
+ observe(payload: unknown, authority: DashboardHostAuthority, selection: DashboardHostConfig["selection"]): DashboardObservation {
14
+ const input=detached(payload) as {sourceId:string;previousCheckpointId:string|null;facts:unknown};
15
+ if(Object.keys(input).sort().join()!=="facts,previousCheckpointId,sourceId")throw Error("exact observation request required");
16
+ const source=c.sources.find(s=>s.id===input.sourceId);if(!source)throw Error("source outside host binding");policy(source.id);
17
+ const previous=latest().find(r=>r.sourceId===source.id)?.checkpointId??null;if(input.previousCheckpointId!==previous)throw Error("stale observation checkpoint");
18
+ const captured=h.ingestPolicySource(c.policyPath,source.id,previous??undefined,c.policySha256);policy(source.id);
19
+ let sourceManifestId:string|null=null,cases:DashboardObservation["cases"]=null,linkageManifestId:string|null=null;
20
+ if(source.kind==="work"||source.kind==="facts"){
21
+ const checkpoint=h.readArchiveCheckpoint(root,captured.checkpointId).checkpoint;sourceManifestId=checkpoint.sourceManifestId;bytes(sourceManifestId);
22
+ if(source.kind==="facts") {if(input.facts!==null)throw Error("facts sources are declarations, not replacement authority");return {sourceId:source.id,kind:source.kind,checkpointId:captured.checkpointId,sourceManifestId,metadata:captured,cases:null,linkageManifestId:null};}
23
+ const factSource=input.facts===null?null:latest().find(r=>r.kind==="facts"&&r.sourceManifestId===input.facts);if(input.facts!==null&&!factSource)throw Error("exact observed fact source required, not caller replacement facts");
24
+ const facts=factSource?parseRetentionJson(new TextDecoder("utf-8",{fatal:true}).decode(bytes(factSource.sourceManifestId!)),256*1024):null;
25
+ captured.factSourceManifestId=factSource?.sourceManifestId??null;captured.factBasis=factSource?"explicit-host-declarations-not-authenticated-truth":"derived-retained-work-only";
26
+ if(dataDigest(authority.workContext.selectedSnapshot)!==dataDigest(selection))throw Error("independent source selection changed");
27
+ try { const signals=h.captureArchivedWorkSignals(root,sourceManifestId,authority.workContext,facts);
28
+ cases={version:"work-signals-v1",batchId:signals.caseBatchId,observationId:signals.observationId};linkageManifestId=signals.linkageManifestId;
29
+ } catch(error) { captured.semanticFailure=String(error); } // Retain checkpoint/gap even when nomination is unavailable.
30
+ } else {if(input.facts!==null||captured.kind!=="execution-retention-v2")throw Error("retention source requires exact referenced-blobs policy");h.readRetainedExecution(root,captured.checkpointId);}
31
+ return {sourceId:source.id,kind:source.kind,checkpointId:captured.checkpointId,sourceManifestId,metadata:captured,cases,linkageManifestId};
32
+ },
33
+ async view(authority: DashboardHostAuthority | null, selection: DashboardHostConfig["selection"]) {
34
+ const rows=latest(), projections:unknown[]=[], manifests:Record<string,Uint8Array>={};let workBytes:Uint8Array|undefined;
35
+ for(const row of rows){policy(row.sourceId);if(row.kind==="work")workBytes=bytes(row.sourceManifestId!);else if(row.kind==="retention"){
36
+ const read=h.readRetainedExecution(root,row.checkpointId);projections.push(read.projection);
37
+ const raw=JSON.parse(new TextDecoder().decode(bytes(row.checkpointId))),manifest=bytes(raw.manifestId);manifests[createHash("sha256").update(manifest).digest("hex")]=manifest;
38
+ }}
39
+ const context=authority?.workContext??{selectedSnapshot:selection,authority:null};
40
+ if(dataDigest(context.selectedSnapshot)!==dataDigest(selection))throw Error("dashboard selection drift");
41
+ const daily=await reader({workBytes,archiveBytes:Buffer.from(JSON.stringify(h.projectRetainedExecutions(projections))),sourceManifestBytes:manifests,workContext:context});
42
+ return {daily,observations:rows,coverage:"partial",unobservedSources:c.sources.filter(s=>!rows.some(r=>r.sourceId===s.id)).map(s=>s.id),sourceJobGaps:history().filter(e=>e.value.type==="claim"&&!history().some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId)).map(e=>String(e.value.requestId)),workerInteractions:0,continuity:"retained-checkpoints-not-live-continuity"};
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,127 @@
1
+ import { createRequire } from "node:module";
2
+ import { createHash } from "node:crypto";
3
+ import { Compile } from "typebox/compile";
4
+ import { parseRetentionJson } from "./retention-json.ts";
5
+ const require = createRequire(import.meta.url);
6
+ const caseCheck = Compile(require("../contracts/debrief/v1/work-capture/work-case.schema.json"));
7
+ const requestCheck = Compile(require("../contracts/debrief/v1/work-capture/review-request.schema.json"));
8
+ export type Disposition = "confirmed_defect" | "expected_behavior" | "exemplar" | "uncertain" | "skip";
9
+ export interface ReviewRequest { caseManifestId: string; priorDecisionId: string | null; disposition: Disposition; note: string }
10
+ export interface ReviewItem { caseManifestId: string; candidate: { id: string; reason: string; [key: string]: unknown }; priorDecisionId: string | null; disposition: Disposition | "unresolved" }
11
+ export interface ReviewPort {
12
+ list(offset: number, limit: number): unknown | Promise<unknown>;
13
+ history(caseManifestId: string): unknown | Promise<unknown>;
14
+ decide?: (request: ReviewRequest) => unknown | Promise<unknown>;
15
+ }
16
+ export interface BlindChoice { kind: "one" | "tie" | "none" | "insufficient"; labels: string[] }
17
+ export interface BlindView { version: "blind-intervention-view-v1"; cards: { label: string; artifactDigests: string[] }[]; limitations: string[] }
18
+ export interface BlindPort {
19
+ quality?(): unknown | Promise<unknown>;
20
+ view(): unknown | Promise<unknown>;
21
+ readArtifact(label: string, digest: string): Uint8Array | Promise<Uint8Array>;
22
+ choose(choice: BlindChoice): unknown | Promise<unknown>;
23
+ reveal(): unknown | Promise<unknown>;
24
+ }
25
+ export const dataKey = (v: unknown): string => Array.isArray(v) ? `[${v.map(dataKey).join(",")}]` : v !== null && typeof v === "object"
26
+ ? `{${Object.keys(v).sort().map(k => JSON.stringify(k) + ":" + dataKey((v as Record<string, unknown>)[k])).join(",")}}` : JSON.stringify(v);
27
+ export const dataDigest = (v: unknown) => createHash("sha256").update(dataKey(v)).digest("hex");
28
+ export const byteDigest = (v: Uint8Array) => createHash("sha256").update(v).digest("hex");
29
+ export const sha = (v: unknown): v is string => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
30
+ export function detached<T>(input: T): T {
31
+ const active = new Set<object>(); let count = 0;
32
+ const inspect = (v: unknown, depth: number): void => {
33
+ if (++count > 50000 || depth > 32) throw new Error("debrief data bound exceeded");
34
+ if (v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v)) return;
35
+ if (!v || typeof v !== "object" || active.has(v)) throw new Error("plain debrief data required");
36
+ const array = Array.isArray(v), prototype = Object.getPrototypeOf(v), fields = Object.getOwnPropertyDescriptors(v);
37
+ if (array ? prototype !== Array.prototype || v.length > 4096 || Reflect.ownKeys(fields).length !== v.length + 1 : ![Object.prototype, null].includes(prototype)) throw new Error("plain debrief object/array required");
38
+ active.add(v);
39
+ for (const k of Reflect.ownKeys(fields)) {
40
+ if (array && k === "length") continue;
41
+ const d = fields[k as string]; if (typeof k !== "string" || !d.enumerable || !Object.hasOwn(d, "value")) throw new Error("hidden/accessor debrief data refused");
42
+ inspect(d.value, depth + 1);
43
+ }
44
+ if (array) for (let i = 0; i < v.length; i++) if (!Object.hasOwn(fields, String(i))) throw new Error("sparse debrief array");
45
+ active.delete(v);
46
+ };
47
+ inspect(input, 0);
48
+ const text = JSON.stringify(input);
49
+ if (!text || Buffer.byteLength(text) > 1024 * 1024) throw new Error("bounded debrief data required");
50
+ return parseRetentionJson(text) as T;
51
+ }
52
+ export function freeze<T>(v: T): T { if (v && typeof v === "object") { Object.values(v).forEach(freeze); Object.freeze(v); } return v; }
53
+ export function closed(v: unknown, names: string[]): asserts v is Record<string, unknown> {
54
+ if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).sort().join() !== [...names].sort().join()) throw new Error("closed debrief object required");
55
+ }
56
+ export function reviewRequest(input: ReviewRequest): ReviewRequest {
57
+ const r = detached(input); if (!requestCheck.Check(r)) throw new Error("invalid label request"); return freeze(r);
58
+ }
59
+ export interface SignalSelection { batchId: string; observationId: string }
60
+ function signalCase(c: Record<string, any>): boolean {
61
+ if (c.capture_schema === 2) return c.reason === "coverage_gap" && c.classification === "coverage_issue" && caseCheck.Check(c) && c.id === dataDigest({ detector: c.detector, target: c.target, reason: c.reason });
62
+ if (c.capture_schema !== 3 || c.classification !== "candidate_defect" || !["overdue_checkpoint", "reopened_acceptance", "intent_conflict"].includes(c.reason)) return false;
63
+ // Signal facts may contribute256 references plus the two base scope/obligation references.
64
+ if (!Array.isArray(c.evidence) || !c.evidence.length || c.evidence.length > 258 || !c.evidence.every(sha)) return false;
65
+ const common = { ...c, capture_schema: 2, reason: "coverage_gap", classification: "coverage_issue", metrics: {}, evidence: c.evidence.slice(0, 256) };
66
+ if (!caseCheck.Check(common) || c.detector.id !== c.reason || !c.detector.version.startsWith("work-signals-v1:") || !c.metrics || typeof c.metrics !== "object") return false;
67
+ const keys = c.reason === "overdue_checkpoint" ? ["deadlineMs", "observedAt"] : c.reason === "intent_conflict" ? ["failedChecks"] : [];
68
+ closed(c.metrics, keys);
69
+ if (keys.some(k => !Number.isSafeInteger(c.metrics[k]) || c.metrics[k] < 0)) return false;
70
+ const m = c.metrics as Record<string, number>;
71
+ if (c.reason === "overdue_checkpoint" && m.observedAt <= m.deadlineMs || c.reason === "intent_conflict" && m.failedChecks < 1) return false;
72
+ const detector = { id: c.detector.id, version: c.detector.version, population: c.detector.population };
73
+ const target = { kind: c.target.kind, snapshotDigest: c.target.snapshotDigest, obligationId: c.target.obligationId, obligationDigest: c.target.obligationDigest };
74
+ const metrics = Object.fromEntries(keys.map(k => [k, c.metrics[k]])), evidence = [...new Set(c.evidence)].sort();
75
+ return dataKey(evidence) === dataKey(c.evidence) && c.id === createHash("sha256").update(JSON.stringify({ capture_schema: 3, detector, target, reason: c.reason, metrics, evidence })).digest("hex");
76
+ }
77
+ export function reviewPage(value: unknown, limit: number, offset = 0, signals?: SignalSelection) {
78
+ const page = detached(value) as { total: number; offset: number; items: ReviewItem[]; observationId?: string; issues?: string[] };
79
+ closed(page, signals ? ["total", "offset", "items", "observationId", "issues"] : ["total", "offset", "items"]);
80
+ if (signals && (page.observationId !== signals.observationId || page.total > 1024 || !Array.isArray(page.issues) || page.issues.length > 1 || page.issues.some(i => i !== "scope-unresolved"))) throw new Error("unbound work signal observation");
81
+ if (!Number.isSafeInteger(page.total) || page.total < 0 || page.total > 4096 || page.offset !== offset || !Array.isArray(page.items) || page.items.length > limit || page.items.length > Math.max(0, page.total - offset)) throw new Error("invalid bounded case page");
82
+ for (const item of page.items) {
83
+ closed(item, ["caseManifestId", "candidate", "priorDecisionId", "disposition"]);
84
+ const c = item.candidate as Record<string, any>;
85
+ if (!sha(item.caseManifestId) || !(signals ? signalCase(c) : caseCheck.Check(c)) || !(item.priorDecisionId === null || sha(item.priorDecisionId)) ||
86
+ !["unresolved", "confirmed_defect", "expected_behavior", "exemplar", "uncertain", "skip"].includes(item.disposition) ||
87
+ (!signals && c.id !== dataDigest({ detector: c.detector, target: c.target, reason: c.reason })) ||
88
+ (!signals && c.classification !== ({ repeat_without_progress: "candidate_defect", economical_exemplar: "candidate_exemplar", coverage_gap: "coverage_issue" } as Record<string, string>)[c.reason]) ||
89
+ (c.reason === "repeat_without_progress" && (c.metrics.equivalentAttempts === undefined || c.metrics.equivalentAttempts < 2)) ||
90
+ (c.reason === "economical_exemplar" && (c.metrics.measuredCost === undefined || c.metrics.costLimit === undefined || c.metrics.costUnit === undefined || c.metrics.measuredCost > c.metrics.costLimit))) throw new Error("invalid case identity/state");
91
+ }
92
+ if (new Set(page.items.map(i => i.caseManifestId)).size !== page.items.length) throw new Error("duplicate case card");
93
+ return freeze(page);
94
+ }
95
+ export function validatedHistory(value: unknown, item: ReviewItem) {
96
+ const history = detached(value) as Record<string, any>[];
97
+ if (!Array.isArray(history) || history.length > 4096) throw new Error("invalid review history");
98
+ let prior: string | null = null;
99
+ for (const d of history) {
100
+ closed(d, ["decision_schema", "id", "caseId", "priorDecisionId", "disposition", "author", "evidence", "note"]);
101
+ const { decision_schema, id, ...body } = d;
102
+ if (decision_schema !== 1 || !sha(id) || dataDigest(body) !== id || d.caseId !== item.candidate.id || d.priorDecisionId !== prior ||
103
+ typeof d.author !== "string" || !d.author || d.author.length > 512 || /[\u0000-\u001f\u007f]/.test(d.author) ||
104
+ !requestCheck.Check({ caseManifestId: item.caseManifestId, priorDecisionId: d.priorDecisionId, disposition: d.disposition, note: d.note }) ||
105
+ !Array.isArray(d.evidence) || d.evidence.length > 256 || !d.evidence.every(sha) || !d.evidence.includes(item.caseManifestId)) throw new Error("unbound review history");
106
+ prior = id;
107
+ }
108
+ return history.at(-1) ?? null;
109
+ }
110
+ export function matchingHistory(value: unknown, item: ReviewItem, request: ReviewRequest, author: string) {
111
+ const last = validatedHistory(value, item);
112
+ const expected = dataDigest({ caseId: item.candidate.id, priorDecisionId: request.priorDecisionId, disposition: request.disposition,
113
+ author, evidence: [item.caseManifestId], note: request.note });
114
+ return last?.id === expected ? expected : null;
115
+ }
116
+ export function blindView(value: unknown): BlindView {
117
+ const view = detached(value) as BlindView; closed(view, ["version", "cards", "limitations"]);
118
+ if (view.version !== "blind-intervention-view-v1" || !Array.isArray(view.cards) || view.cards.length < 2 || view.cards.length > 4 || !Array.isArray(view.limitations) || view.limitations.some(v => typeof v !== "string")) throw new Error("unsupported blind view");
119
+ for (const c of view.cards) { closed(c, ["label", "artifactDigests"]); if (!/^variant-[a-f0-9]{16}$/.test(c.label) || !Array.isArray(c.artifactDigests) || c.artifactDigests.length > 4 || !c.artifactDigests.every(sha)) throw new Error("invalid opaque artifact card"); }
120
+ if (new Set(view.cards.map(c => c.label)).size !== view.cards.length) throw new Error("duplicate blind label"); return freeze(view);
121
+ }
122
+ export function blindChoice(input: BlindChoice, view: BlindView): BlindChoice {
123
+ const c = detached(input); closed(c, ["kind", "labels"]);
124
+ if (!Array.isArray(c.labels) || new Set(c.labels).size !== c.labels.length || c.labels.some(l => !view.cards.some(v => v.label === l)) ||
125
+ !["one", "tie", "none", "insufficient"].includes(c.kind) || (c.kind === "one" ? c.labels.length !== 1 : c.kind === "tie" ? c.labels.length < 2 : c.labels.length !== 0)) throw new Error("invalid quality choice");
126
+ return freeze({ kind: c.kind, labels: [...c.labels].sort() });
127
+ }
@@ -0,0 +1,29 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createDebriefPresenter, type DebriefCheckpoint, type DebriefPersistence } from "./debrief.ts";
3
+ import { blindView, blindChoice, dataKey, dataDigest, detached, freeze, type BlindChoice } from "./debrief-contract.ts";
4
+ import { parseRetentionJson } from "./retention-json.ts";
5
+ const root = new URL("../contracts/debrief/v1/", import.meta.url);
6
+ const json = async (path: string) => parseRetentionJson(await readFile(new URL(path, root), "utf8")) as any;
7
+ /** Explicit frozen demo only. No case writer or durable blind/attention qualification is fabricated. */
8
+ export async function createFixtureDebrief() {
9
+ const cases = await json("work-capture/fixtures/cases.json"), batch = await json("work-capture/fixtures/batch.json");
10
+ if (cases.length !== 7 || batch.candidateIds.length !== 7) throw new Error("fixture inventory changed");
11
+ const items = cases.map((candidate: any, i: number) => ({ candidate, caseManifestId: batch.candidateIds[i], priorDecisionId: null, disposition: "unresolved" })).sort((a: any, b: any) => a.caseManifestId.localeCompare(b.caseManifestId));
12
+ const view = blindView(await json("intervention/fixtures/blind-view.json"));
13
+ let choice: BlindChoice | null = null, revealed = false, state: DebriefCheckpoint | null = null;
14
+ const persistence: DebriefPersistence = { durability: "fixture-memory", load: () => detached(state), compareAndSwap(expected, next) {
15
+ if (expected !== (state ? dataDigest(state) : null)) throw new Error("stale fixture checkpoint"); state = freeze(detached(next));
16
+ } };
17
+ return createDebriefPresenter({ scope: "manual-frozen-fixture-v1", fixture: true, persistence,
18
+ reviewer: { list: (offset, limit) => ({ total: 7, offset, items: items.slice(offset, offset + limit) }), history: () => [] },
19
+ blind: { view: () => view, readArtifact: async (label, digest) => {
20
+ if (!view.cards.some(c => c.label === label && c.artifactDigests.includes(digest))) throw new Error("artifact outside fixture blind view");
21
+ return readFile(new URL(`intervention/fixtures/artifact-${digest}.txt`, root));
22
+ }, choose(input) {
23
+ const next = blindChoice(input, view); if (revealed && dataKey(choice) !== dataKey(next)) throw new Error("fixture choice locked"); choice = next; return next;
24
+ }, async reveal() {
25
+ if (!choice) throw new Error("quality first"); const saved = await json("intervention/fixtures/reveal.json");
26
+ revealed = true; return { ...saved, choice };
27
+ } },
28
+ });
29
+ }
@@ -0,0 +1,53 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { createDebriefPresenter, type DebriefPersistence } from "./debrief.ts";
3
+ import { closed, detached, freeze, sha, type BlindChoice, type BlindPort, type ReviewPort } from "./debrief-contract.ts";
4
+ /** Host supplies its loaded harness module, not worker metadata or a permission-shaped receipt.
5
+ * Ordinary integration consumes exact638494af... source bytes; this structural port is not module authentication. */
6
+ export interface DebriefHarness {
7
+ createWorkCaseReviewer(root: string, batchId: string, author: string): ReviewPort;
8
+ createWorkSignalReviewer(root: string, batchId: string, author: string): ReviewPort;
9
+ retainBlindIntervention(root: string, manifest: unknown, evidence: unknown[], qualification: unknown, author: string): string;
10
+ openBlindIntervention(root: string, id: string, author: string): BlindPort & { quality(): unknown };
11
+ }
12
+ export interface DurableBlindBinding { comparisonId: string; author: string }
13
+ export type CaseSelection = { version: "work-case-v2"; batchId: string } | { version: "work-signals-v1"; batchId: string; observationId: string };
14
+ function location(root: string, author: string) {
15
+ if (typeof root !== "string" || !isAbsolute(root) || typeof author !== "string" || !author || author.length > 512 || /[\u0000-\u001f\u007f]/.test(author)) throw new Error("explicit archive and independent operator author required");
16
+ }
17
+ function binding(input: DurableBlindBinding): DurableBlindBinding {
18
+ const value = detached(input); closed(value, ["comparisonId", "author"]);
19
+ if (!sha(value.comparisonId) || typeof value.author !== "string") throw new Error("exact retained comparison binding required");
20
+ return freeze(value);
21
+ }
22
+ /** Explicit host retention action only. Caller must retain returned identity independently before exposure. */
23
+ export function retainDebriefBlind(harness: DebriefHarness, root: string, input: { manifest: unknown; evidence: unknown[]; qualification: unknown }, author: string): DurableBlindBinding {
24
+ location(root, author);
25
+ const comparisonId = harness.retainBlindIntervention(root, input.manifest, input.evidence, input.qualification, author);
26
+ return binding({ comparisonId, author });
27
+ }
28
+ /** No choose/reveal/archive/seed/role/cost capability is given to a pre-reveal consumer. */
29
+ export function openDebriefBlindPreview(harness: DebriefHarness, root: string, input: DurableBlindBinding) {
30
+ const b = binding(input); location(root, b.author);
31
+ const port = harness.openBlindIntervention(root, b.comparisonId, b.author);
32
+ return Object.freeze({ view: port.view.bind(port), readArtifact: port.readArtifact.bind(port), quality: port.quality.bind(port) });
33
+ }
34
+ /** Existing presenter + existing durable writers. No second decision history or automatic retention/reveal. */
35
+ export function createRetainedDebrief(harness: DebriefHarness, input: {
36
+ archiveRoot: string; scope: string; author: string; cases: CaseSelection; blind?: DurableBlindBinding;
37
+ persistence: DebriefPersistence; offset?: number;
38
+ }) {
39
+ const root = input.archiveRoot, author = input.author, selection = freeze(detached(input.cases)); location(root, author);
40
+ if (selection.version !== "work-case-v2" && selection.version !== "work-signals-v1") throw new Error("explicit case version required");
41
+ closed(selection, selection.version === "work-signals-v1" ? ["version", "batchId", "observationId"] : ["version", "batchId"]);
42
+ if (!sha(selection.batchId) || selection.version === "work-signals-v1" && !sha(selection.observationId)) throw new Error("exact selected batch/observation required");
43
+ const reviewer = selection.version === "work-signals-v1" ? harness.createWorkSignalReviewer(root, selection.batchId, author) : harness.createWorkCaseReviewer(root, selection.batchId, author);
44
+ const blindBinding = input.blind ? binding(input.blind) : undefined;
45
+ if (blindBinding && blindBinding.author !== author) throw new Error("blind author differs from independent operator");
46
+ const open = harness.openBlindIntervention.bind(harness);
47
+ const blind = blindBinding ? { ...openDebriefBlindPreview(harness, root, blindBinding),
48
+ choose: (choice: BlindChoice) => open(root, blindBinding.comparisonId, author).choose(choice),
49
+ reveal: () => open(root, blindBinding.comparisonId, author).reveal() } : undefined;
50
+ return createDebriefPresenter({ scope: input.scope, offset: input.offset, operatorIdentity: author, reviewer, blind, blindBinding,
51
+ signals: selection.version === "work-signals-v1" ? { batchId: selection.batchId, observationId: selection.observationId } : undefined,
52
+ persistence: input.persistence });
53
+ }
@@ -0,0 +1,49 @@
1
+ import type { DebriefFrame, DebriefPresenter } from "./debrief.ts";
2
+ import { isDebriefPresenter } from "./debrief.ts";
3
+ const safe = (v: unknown) => String(v).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ");
4
+ export function renderDebrief(view: DebriefFrame, width = 100): string {
5
+ const lines = ["PI-DADDY — MANUAL DEBRIEF / WEEKLY QUEUE", `State: ${view.state}; automatic presentation UNQUALIFIED`,
6
+ `Attention: ${view.budgetSpent ?? "UNKNOWN"}/5 allocated slots; ${view.unexposed} incidents not exposed; persistence: ${view.persistence}`,
7
+ view.fixture ? "FROZEN SYNTHETIC FIXTURE — public seed/artifacts, NOT a real blind study" : "Host identity, pause policy and durability are not authenticated by this view."];
8
+ if (view.observation) lines.push(`Selected signal batch: ${view.total} cases; observation ${view.observation.id}`,
9
+ `Observation issues: ${view.observation.issues.join(", ") || "none recorded"}; counts are selected-batch only.`);
10
+ if (view.remainingBudget === null) lines.push("Attention persistence UNKNOWN — no new exposure allowance is inferred.");
11
+ if (view.remainingBudget === 0) lines.push("New questions DEFERRED — budget exhausted. Reopen retains the same slots; answers/skip do not refill them.");
12
+ for (const card of view.cards) {
13
+ if (card.kind === "case") lines.push(`${card.slot}. CASE — ${card.summary}`, ` ${card.disposition} / ${card.resolution}; labels ${card.actionEnabled ? "enabled" : "unavailable or already submitted"}`);
14
+ else {
15
+ lines.push(`${card.slot}. BLIND QUALITY — one shared slot; ${card.choice ? card.choice.kind : "UNSELECTED / UNRESOLVED"}`);
16
+ for (const variant of card.variants) lines.push(` ${variant.label}: ${variant.text.join(" | ")}`);
17
+ lines.push(" Filenames/captions/metadata omitted; arbitrary artifact text may disclose identity.");
18
+ if (card.revealed) {
19
+ lines.push(" AFTER QUALITY CHOICE — no routing/adoption authorized");
20
+ for (const arm of (card.revealed as { arms: { label: string; cost: number | null; costUnit: string; configuration: Record<string, unknown> }[] }).arms) {
21
+ lines.push(` ${arm.label}: ${arm.cost ?? "unknown"} ${arm.costUnit}`);
22
+ for (const [key, value] of Object.entries(arm.configuration)) lines.push(` ${key}: ${String(value)}`);
23
+ }
24
+ }
25
+ else lines.push(` Configuration/cost hidden; reveal ${card.choiceConfirmed ? "available" : "locked"}. Choices: one / tie / none / insufficient.`);
26
+ }
27
+ }
28
+ lines.push("Skip and unselected remain unresolved. Labels are not work acceptance, causal proof or routing/adoption.",
29
+ "Own dashboard commands: label SLOT DISPOSITION NOTE | choose KIND LABEL... | reveal | reconcile SLOT | close | open",
30
+ "No worker messages, control dispatch or automatic recovery. Missing host callbacks disable actions.");
31
+ const w = Math.max(40, Math.min(240, Math.floor(width) || 100));
32
+ return lines.slice(0, 80).map(line => { const text = safe(line); return text.length > w ? text.slice(0, w - 1) + "…" : text; }).join("\n");
33
+ }
34
+ /** The actual existing dashboard action seam; no terminal/worker transport or authority metadata. */
35
+ export async function debriefAction(presenter: DebriefPresenter, command: string): Promise<unknown> {
36
+ if (!isDebriefPresenter(presenter) || typeof command !== "string" || command.length > 4096) throw new Error("invalid debrief action");
37
+ const [action, ...args] = command.trim().split(/\s+/), view = presenter.view();
38
+ if (action === "close" && !args.length) { return presenter.close(); }
39
+ if (action === "open" && !args.length) { return presenter.open({ mode: "manual", userPresent: true }); }
40
+ if (action === "reveal" && !args.length) { return presenter.reveal(); }
41
+ if (action === "choose") { return presenter.choose({ kind: args[0] as "one", labels: args.slice(1) }); }
42
+ const slot = Number(args[0]), card = view.cards.find(c => c.slot === slot);
43
+ if (card?.kind !== "case") throw new Error("exact exposed case slot required");
44
+ if (action === "reconcile" && args.length === 1) { return presenter.reconcile(card.caseManifestId); }
45
+ if (action === "label" && args.length >= 2) {
46
+ return presenter.label({ caseManifestId: card.caseManifestId, priorDecisionId: card.priorDecisionId, disposition: args[1] as "skip", note: args.slice(2).join(" ") });
47
+ }
48
+ throw new Error("unsupported debrief action");
49
+ }