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,44 @@
1
+ import type { Delegation } from "../src/delegate.ts";
2
+ import { retentionConfigurationDigest } from "../src/execution-retention.ts";
3
+ import { appendDeclaredWorkOccurrence } from "../src/work-command.ts";
4
+ import type { WorkOccurrencePayload } from "../src/work-ledger.ts";
5
+ import type { GrantsSession } from "./session.ts";
6
+ import type { PreparedWorkspace } from "./workspace-runtime.ts";
7
+
8
+ export async function beginDeclaredWorkAttempt(input: {
9
+ session: GrantsSession;
10
+ plan: Delegation;
11
+ childId: string;
12
+ executionId: string;
13
+ parentExecutionId: string | null;
14
+ toolCallId?: string;
15
+ preparedWorkspace?: PreparedWorkspace;
16
+ configuredTimeoutMs: number;
17
+ startedAt: Date;
18
+ }): Promise<null | { finish(state: Extract<WorkOccurrencePayload["state"], "completed" | "failed">): Promise<void> }> {
19
+ const state = input.session.declaredWork;
20
+ if (!state) return null;
21
+ const argument = (flag: string): string | null => {
22
+ const index = input.plan.args.indexOf(flag);
23
+ return index >= 0 ? input.plan.args[index + 1] ?? null : null;
24
+ };
25
+ const identity = {
26
+ executionId: input.executionId,
27
+ parentExecutionId: input.parentExecutionId,
28
+ childId: input.childId,
29
+ variantId: input.plan.correlation?.context_id ?? null,
30
+ toolCallId: input.toolCallId ?? null,
31
+ taskId: input.plan.correlation?.task_id ?? input.plan.taskDigest,
32
+ workspaceId: input.preparedWorkspace?.workspace.workspaceId ?? input.plan.correlation?.workspace_id ?? null,
33
+ definitionDigest: input.plan.definitionDigest?.sha256 ?? null,
34
+ configurationDigest: retentionConfigurationDigest({
35
+ args: input.plan.args,
36
+ effective: input.plan.effective,
37
+ timeoutMs: input.configuredTimeoutMs,
38
+ }),
39
+ modelId: argument("--model"),
40
+ effortId: argument("--thinking"),
41
+ };
42
+ await appendDeclaredWorkOccurrence(state, { ...identity, now: input.startedAt }, "starting");
43
+ return { finish: (terminal) => appendDeclaredWorkOccurrence(state, { ...identity, now: new Date() }, terminal) };
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-daddy",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Capability governance for pi sub-agents: spawn Agent Skills (SKILL.md) definitions whose allowed-tools becomes a grant that can only narrow going down a delegation tree, enforced by pi's own --tools allowlist, with an append-only ledger.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -37,6 +37,67 @@
37
37
  "types": "./dist/ledger.d.ts",
38
38
  "default": "./dist/ledger.js"
39
39
  },
40
+ "./work-ledger": {
41
+ "types": "./dist/work-ledger.d.ts",
42
+ "default": "./dist/work-ledger.js"
43
+ },
44
+ "./factory-order": {
45
+ "types": "./dist/factory-order.d.ts",
46
+ "default": "./dist/factory-order.js"
47
+ },
48
+ "./experiment": {
49
+ "types": "./dist/experiment.d.ts",
50
+ "default": "./dist/experiment.js"
51
+ },
52
+ "./dashboard-harness": {
53
+ "types": "./dist/dashboard-harness.d.ts",
54
+ "default": "./dist/dashboard-harness.js"
55
+ },
56
+ "./dashboard-host": {
57
+ "types": "./dist/dashboard-host.d.ts",
58
+ "default": "./dist/dashboard-host.js"
59
+ },
60
+ "./dashboard-host-transport": {
61
+ "types": "./dist/dashboard-host-transport.d.ts",
62
+ "default": "./dist/dashboard-host-transport.js"
63
+ },
64
+ "./debrief-host": {
65
+ "types": "./dist/debrief-host.d.ts",
66
+ "default": "./dist/debrief-host.js"
67
+ },
68
+ "./contracts/debrief/v2/*": "./contracts/debrief/v2/*",
69
+ "./debrief": {
70
+ "types": "./dist/debrief.d.ts",
71
+ "default": "./dist/debrief.js"
72
+ },
73
+ "./daily-view": {
74
+ "types": "./dist/daily-view.d.ts",
75
+ "default": "./dist/daily-view.js"
76
+ },
77
+ "./intent-control": {
78
+ "types": "./dist/intent-control.d.ts",
79
+ "default": "./dist/intent-control.js"
80
+ },
81
+ "./dispatch-control": {
82
+ "types": "./dist/dispatch-control.d.ts",
83
+ "default": "./dist/dispatch-control.js"
84
+ },
85
+ "./resource-budget": {
86
+ "types": "./dist/resource-budget.d.ts",
87
+ "default": "./dist/resource-budget.js"
88
+ },
89
+ "./producer-ipc": {
90
+ "types": "./dist/producer-ipc.d.ts",
91
+ "default": "./dist/producer-ipc.js"
92
+ },
93
+ "./effect-profile": {
94
+ "types": "./dist/effect-profile.d.ts",
95
+ "default": "./dist/effect-profile.js"
96
+ },
97
+ "./execution-retention": {
98
+ "types": "./dist/execution-retention.d.ts",
99
+ "default": "./dist/execution-retention.js"
100
+ },
40
101
  "./spawn": {
41
102
  "types": "./dist/spawn.d.ts",
42
103
  "default": "./dist/spawn.js"
@@ -137,6 +198,19 @@
137
198
  "types": "./dist/dashboard-render.d.ts",
138
199
  "default": "./dist/dashboard-render.js"
139
200
  },
201
+ "./contracts/factory-order/v1/*": "./contracts/factory-order/v1/*",
202
+ "./contracts/experiment/v1/*": "./contracts/experiment/v1/*",
203
+ "./contracts/producer-ipc/v1/*": "./contracts/producer-ipc/v1/*",
204
+ "./contracts/debrief/v1/*": "./contracts/debrief/v1/*",
205
+ "./contracts/intent-control/v2/request.schema.json": "./contracts/intent-control/v2/request.schema.json",
206
+ "./contracts/intent-control/v1/request.schema.json": "./contracts/intent-control/v1/request.schema.json",
207
+ "./contracts/dispatch-control/v1/request.schema.json": "./contracts/dispatch-control/v1/request.schema.json",
208
+ "./contracts/daily-view/v1/fixtures/*": "./contracts/daily-view/v1/fixtures/*",
209
+ "./contracts/daily-view/v1/p03/projection.schema.json": "./contracts/daily-view/v1/p03/projection.schema.json",
210
+ "./contracts/execution-retention/v2/manifest.schema.json": "./contracts/execution-retention/v2/manifest.schema.json",
211
+ "./contracts/execution-retention/v2/fixtures/*": "./contracts/execution-retention/v2/fixtures/*",
212
+ "./contracts/ledger/v4/ledger-event.schema.json": "./contracts/ledger/v4/ledger-event.schema.json",
213
+ "./contracts/ledger/v4/fixtures/*.json": "./contracts/ledger/v4/fixtures/*.json",
140
214
  "./contracts/ledger/v3/ledger-event.schema.json": "./contracts/ledger/v3/ledger-event.schema.json",
141
215
  "./contracts/ledger/v3/fixtures/*.json": "./contracts/ledger/v3/fixtures/*.json",
142
216
  "./contracts/ledger/v2/ledger-event.schema.json": "./contracts/ledger/v2/ledger-event.schema.json",
@@ -159,13 +233,13 @@
159
233
  "scripts": {
160
234
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
161
235
  "typecheck": "tsc -p tsconfig.check.json",
162
- "test": "node --test test/*.test.ts",
236
+ "test": "node scripts/unit-tests.ts",
163
237
  "test:integration": "node --test test-integration/*.it.ts",
164
238
  "test:integration:ci": "node --test test-integration/approval.it.ts test-integration/governance.it.ts",
165
239
  "contracts:generate": "node scripts/generate-ledger-v3-contract.ts",
240
+ "contracts:generate:v4": "node scripts/generate-ledger-v4-contract.ts contracts/ledger/v4",
166
241
  "prepack": "npm run build",
167
- "test:smoke": "node scripts/smoke-installed.mjs",
168
- "test:mutation": "node scripts/mutation-audit.mjs"
242
+ "test:smoke": "node scripts/smoke-installed.mjs"
169
243
  },
170
244
  "main": "./dist/index.js",
171
245
  "types": "./dist/index.d.ts",
@@ -1,4 +1,5 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
+ import { beginExecutionRetention, retentionConfigurationDigest, type RetentionStatus } from "./execution-retention.ts";
2
3
  import { chmod, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
3
4
  import { tmpdir } from "node:os";
4
5
  import { basename, isAbsolute, join } from "node:path";
@@ -128,11 +129,15 @@ export async function runNamedCheck(input: {
128
129
  signal?: AbortSignal;
129
130
  leaseDir?: string;
130
131
  ledgerPath?: string;
132
+ /** Explicit host archive boundary; defaults to PI_GRANTS_EXECUTION_ARCHIVE, never correlation. */
133
+ retentionDirectory?: string;
134
+ toolCallId?: string;
131
135
  }): Promise<{
132
136
  output: string;
133
137
  exitCode: number | null;
134
138
  signal: NodeJS.Signals | null;
135
139
  receipt: CheckReceipt;
140
+ retention: RetentionStatus;
136
141
  }> {
137
142
  if (input.registry?.version !== 1 || !input.registry.checks || typeof input.registry.checks !== "object") {
138
143
  throw new GovernanceRefusal(refusal("CHECK_CONFIGURATION_INVALID", "check registry must contain {version:1, checks:{...}}"));
@@ -159,11 +164,17 @@ export async function runNamedCheck(input: {
159
164
  const executionId = newExecutionId();
160
165
  const parentExecutionId = null;
161
166
  const correlation = normaliseCorrelation(input.correlation);
167
+ const retention = beginExecutionRetention({ executionId, parentExecutionId, childId: ownerId,
168
+ toolCallId: input.toolCallId ?? null, executor: "check", taskDigest: null, definitionDigest: null,
169
+ configurationDigest: retentionConfigurationDigest({ executable, argv: definition.argv, access,
170
+ timeoutMs: definition.timeout_ms ?? null, maxOutputBytes: definition.max_output_bytes ?? null }),
171
+ workspaceId: input.workspace.workspaceId }, input.retentionDirectory);
162
172
  let lease: Awaited<ReturnType<typeof acquireWorkspaceLease>> | undefined;
163
173
  let stagedDir: string | undefined;
164
174
  /** Failures of best-effort RECORDS, reported alongside whatever actually happened — never instead of it. */
165
175
  const notes: string[] = [];
166
176
  let releaseReason = "failed";
177
+ let observedOutcome = { code: null as number | null, signal: null as string | null, timedOut: false, aborted: false, truncated: false };
167
178
  return runWithFinalizers(async () => {
168
179
  try {
169
180
  try {
@@ -228,8 +239,11 @@ export async function runNamedCheck(input: {
228
239
  command: "setpriv", args: ["--pdeathsig", "KILL", "--", stagedExecutable, ...definition.argv],
229
240
  env: buildCheckEnvironment(definition, input.inheritedEnv), cwd: input.workspace.root,
230
241
  signal: checkSignal, timeoutMs: definition.timeout_ms, maxOutputBytes: definition.max_output_bytes,
231
- onSpawn: (pid) => lease!.attachProcess(pid),
242
+ onSpawn: (pid) => { lease!.attachProcess(pid); retention.native({ pid }); },
243
+ onObservation: (stream, bytes) => retention.capture(stream, bytes),
232
244
  });
245
+ observedOutcome = { code: result.code, signal: result.signal ?? null, timedOut: result.timedOut, aborted: result.aborted, truncated: result.truncated };
246
+ retention.capture("result", Buffer.from(result.text), true);
233
247
  const ended = new Date();
234
248
  releaseReason = input.signal?.aborted ? "cancelled" : result.timedOut ? "timeout" : result.aborted ? "failed" : "completed";
235
249
  // Every throw below re-stamps this. Without that, a check refused for CHECK_IDENTITY_MISMATCH still
@@ -285,6 +299,7 @@ export async function runNamedCheck(input: {
285
299
  head_sha: before.headSha, tree_sha: before.treeSha, ...(correlation ? { correlation } : {}),
286
300
  };
287
301
  const receipt: CheckReceipt = { receipt_id: receiptId(body), ...body };
302
+ retention.capture("checkReceipt", Buffer.from(JSON.stringify(receipt) + "\n"), true);
288
303
  if (input.ledgerPath) {
289
304
  await appendLedgerEvent(
290
305
  { path: input.ledgerPath, strict: true },
@@ -295,8 +310,11 @@ export async function runNamedCheck(input: {
295
310
  }),
296
311
  );
297
312
  }
298
- return { output: result.text, exitCode: result.code, signal: result.signal ?? null, receipt };
313
+ retention.finish({ code: result.code, signal: result.signal ?? null, timedOut: result.timedOut,
314
+ aborted: result.aborted, truncated: result.truncated, failed: result.code !== 0 });
315
+ return { output: result.text, exitCode: result.code, signal: result.signal ?? null, receipt, retention: retention.status() };
299
316
  } catch (error) {
317
+ retention.finish({ ...observedOutcome, aborted: observedOutcome.aborted || Boolean(input.signal?.aborted), failed: true });
300
318
  // The run did not complete, so the release must not be recorded as if it had. Without this the
301
319
  // `finally` wrote `released / completed` as the LAST lease event for a refused check, so the trail
302
320
  // still read like a clean run and `verifyLedger` still counted a clean release — the R-114 fix put a
package/src/cli.ts CHANGED
@@ -21,12 +21,15 @@ import { UnsafeGrantError } from "./grant-env.ts";
21
21
  import { applyInit, countDeclaring, planInit, type InitPlan } from "./init.ts";
22
22
  import { registeredWorkspaceIds } from "./workspace.ts";
23
23
  import { discoverSkillPackages, skillPackageRoots, type RefusedSkill, type SkillPackage } from "./skill-packages.ts";
24
+ import { declareWork } from "./work-command.ts";
24
25
 
25
26
  const USAGE = `pi-daddy — capability governance for pi sub-agents
26
27
 
27
28
  Usage:
28
29
  pi-daddy init [--force] [--dir <path>] scaffold .pi/skills/ and .pi/grants.env from installed
29
30
  packages that declare skills (package.json "pi": {"skills": …})
31
+ pi-daddy work add --id <id> --outcome <text> [--dir <path>]
32
+ declare one current obligation for ordinary delegation
30
33
  pi-daddy --help | --version
31
34
 
32
35
  init copies each declared SKILL.md into .pi/skills/ and writes a grant naming exactly what those files
@@ -38,9 +41,11 @@ placeholder and stays unspawnable until you fill it in. Capabilities that can ch
38
41
  It never rewrites .pi/grants.env — delete that file if you want it regenerated.`;
39
42
 
40
43
  export interface ParsedArgs {
41
- command: "init" | "help" | "version";
44
+ command: "init" | "work-add" | "help" | "version";
42
45
  dir?: string;
43
46
  force: boolean;
47
+ id?: string;
48
+ outcome?: string;
44
49
  /** Non-empty means refuse: argv said something this program does not understand. */
45
50
  errors: string[];
46
51
  }
@@ -58,16 +63,28 @@ export function parseArgs(argv: string[]): ParsedArgs {
58
63
  if (args.includes("--help") || args.includes("-h")) return { command: "help", force: false, errors: [] };
59
64
  if (args.includes("--version") || args.includes("-v")) return { command: "version", force: false, errors: [] };
60
65
 
61
- const [command, ...rest] = args;
62
- if (command !== "init") return { command: "help", force: false, errors: [`unknown command "${command}"`] };
66
+ const [command, ...tail] = args;
67
+ if (command !== "init" && command !== "work") return { command: "help", force: false, errors: [`unknown command "${command}"`] };
68
+ const work = command === "work";
69
+ const rest = work && tail[0] === "add" ? tail.slice(1) : tail;
70
+ if (work && tail[0] !== "add") return { command: "work-add", force: false, errors: ["work needs subcommand add"] };
63
71
 
64
72
  const errors: string[] = [];
65
73
  let dir: string | undefined;
66
74
  let force = false;
75
+ let id: string | undefined, outcome: string | undefined;
67
76
  for (let i = 0; i < rest.length; i += 1) {
68
77
  const arg = rest[i];
69
- if (arg === "--force") {
78
+ if (arg === "--force" && !work) {
70
79
  force = true;
80
+ } else if (arg === "--id" && work) {
81
+ const value = rest[i + 1];
82
+ if (value === undefined || value.startsWith("-")) errors.push("--id needs a value");
83
+ else { id = value; i += 1; }
84
+ } else if (arg === "--outcome" && work) {
85
+ const value = rest[i + 1];
86
+ if (value === undefined || value.startsWith("-")) errors.push("--outcome needs text");
87
+ else { outcome = value; i += 1; }
71
88
  } else if (arg === "--dir") {
72
89
  const value = rest[i + 1];
73
90
  // A flag is not a path. Without this, `--dir --force` consumed the flag as the directory AND left
@@ -87,6 +104,11 @@ export function parseArgs(argv: string[]): ParsedArgs {
87
104
  }
88
105
  }
89
106
 
107
+ if (work) {
108
+ if (!id && !errors.some(error => error.startsWith("--id"))) errors.push("--id needs a value");
109
+ if (!outcome && !errors.some(error => error.startsWith("--outcome"))) errors.push("--outcome needs text");
110
+ return { command: "work-add", force: false, errors, ...(id ? { id } : {}), ...(outcome ? { outcome } : {}), ...(dir ? { dir } : {}) };
111
+ }
90
112
  return { command: "init", dir, force, errors };
91
113
  }
92
114
 
@@ -260,6 +282,21 @@ export async function main(argv: string[]): Promise<number> {
260
282
  console.log(USAGE);
261
283
  return 0;
262
284
  }
285
+ if (parsed.command === "work-add") {
286
+ const declared = await declareWork({
287
+ cwd: resolvePath(parsed.dir ?? process.cwd()),
288
+ id: parsed.id!,
289
+ outcome: parsed.outcome!,
290
+ });
291
+ console.log(
292
+ `pi-daddy work: declared ${declared.id}\n` +
293
+ ` ledger ${declared.ledgerPath}\n` +
294
+ ` selection ${declared.statePath}\n\n` +
295
+ `Ordinary governed delegations from this project will record attempts after the Pi extension loads it.\n` +
296
+ `Run /reload in an existing Pi session, then /grants dashboard. Runtime success is not acceptance.`,
297
+ );
298
+ return 0;
299
+ }
263
300
 
264
301
  // ABSOLUTE, always. `readSkill` compares a resolved entry against the package directory, and a relative
265
302
  // `--dir` made that comparison false for every entry: every declared skill of every package was reported
@@ -0,0 +1,26 @@
1
+ import { constants } from "node:fs";
2
+ import { mkdir, open, lstat } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { withFileLock } from "./file-lock.ts";
5
+ import { runWithFinalizers } from "./finalization.ts";
6
+ import { ownedDirectory, readExperimentFile } from "./experiment-store.ts";
7
+ import { cloneExperiment, closed, experimentHash } from "./experiment-contract.ts";
8
+ import { freezeWork, parseWorkJson } from "./work-ledger-json.ts";
9
+ export interface ControlBinding<T> {version:"control-journal-v1";directory:string;device:string;inode:string;journalDevice:string;journalInode:string;initial:T}
10
+ export async function createControlJournal<T>(directory:string,input:T):Promise<ControlBinding<T>>{
11
+ const initial=cloneExperiment(input);if(typeof directory!=="string"||directory!==resolve(directory)||directory.length>1024||directory.split("/").includes(".pi"))throw new Error("explicit control directory required");
12
+ await ownedDirectory(dirname(directory));await mkdir(directory,{mode:0o700});const root=await ownedDirectory(directory),f=await open(join(directory,"control.jsonl"),"wx",0o600);
13
+ const b=await runWithFinalizers(async()=>{const s=await f.stat({bigint:true});const b={version:"control-journal-v1" as const,directory,device:String(root.dev),inode:String(root.ino),journalDevice:String(s.dev),journalInode:String(s.ino),initial};await f.writeFile(JSON.stringify(b)+"\n");await f.sync();return b;},[{label:"control creation close failed",run:()=>f.close()}]);
14
+ const d=await open(directory,constants.O_RDONLY|constants.O_DIRECTORY);await runWithFinalizers(()=>d.sync(),[{label:"control directory close failed",run:()=>d.close()}]);return freezeWork(b) as unknown as ControlBinding<T>;
15
+ }
16
+ export function controlJournal<T>(input:ControlBinding<T>){
17
+ const b=freezeWork(cloneExperiment(input)) as unknown as ControlBinding<T>;closed(b,["version","directory","device","inode","journalDevice","journalInode","initial"]);
18
+ if(b.version!=="control-journal-v1"||b.directory!==resolve(b.directory)||b.directory.split("/").includes(".pi")||[b.device,b.inode,b.journalDevice,b.journalInode].some(v=>!/^\d+$/.test(v)))throw new Error("invalid control binding");
19
+ const path=join(b.directory,"control.jsonl");
20
+ const check=async()=>{const root=await ownedDirectory(b.directory),file=await lstat(path,{bigint:true});if(String(root.dev)!==b.device||String(root.ino)!==b.inode||String(file.dev)!==b.journalDevice||String(file.ino)!==b.journalInode)throw new Error("control identity changed");};
21
+ const read=async()=>{await check();const bytes=await readExperimentFile(path,512*1024);await check();const text=new TextDecoder("utf8",{fatal:true}).decode(bytes);if(!text.endsWith("\n"))throw new Error("torn control journal");const lines=text.slice(0,-1).split("\n");if(lines.length>257||experimentHash(parseWorkJson(lines[0]))!==experimentHash(b))throw new Error("control header/limit mismatch");let previous=experimentHash(b);const events:Record<string,unknown>[]=[];
22
+ for(const line of lines.slice(1)){const r=parseWorkJson(line) as Record<string,unknown>;closed(r,["sequence","previous","event"]);if(r.sequence!==events.length||r.previous!==previous||!r.event||typeof r.event!=="object"||Array.isArray(r.event))throw new Error("invalid control sequence");previous=experimentHash(r);events.push(r.event as Record<string,unknown>);}return{events,previous,size:bytes.length};};
23
+ return {binding:b,read,async transaction<R>(fn:(events:Record<string,unknown>[],append:(e:unknown)=>Promise<void>)=>Promise<R>){await check();return withFileLock(path,"factory policy",async()=>{const state=await read();return fn(state.events,async event=>{if(state.events.length>=256)throw new Error("control event budget exhausted");const record={sequence:state.events.length,previous:state.previous,event:cloneExperiment(event)},data=Buffer.from(JSON.stringify(record)+"\n");if(state.size+data.length>512*1024||data.length>65536)throw new Error("control byte budget exhausted");await check();const f=await open(path,constants.O_WRONLY|constants.O_APPEND|constants.O_NOFOLLOW|constants.O_NONBLOCK);
24
+ await runWithFinalizers(async()=>{const s=await f.stat({bigint:true});if(!s.isFile()||s.nlink!==1n||String(s.dev)!==b.journalDevice||String(s.ino)!==b.journalInode||s.size!==BigInt(state.size)||(s.mode&0o077n)!==0n)throw new Error("control journal changed under lock");await f.writeFile(data);await f.sync();},[{label:"control append close failed",run:()=>f.close()}]);await check();state.size+=data.length;state.previous=experimentHash(record);state.events.push(cloneExperiment(event) as Record<string,unknown>);
25
+ });},{staleRecovery:"disabled"});}};
26
+ }
@@ -0,0 +1,49 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { isAbsolute, resolve, join } from "node:path";
4
+ import { createDashboardHost, dashboardHostDigest, dashboardHostRequestDigest, type DashboardHostAuthority, type DashboardHostConfig, type DashboardHostRequest } from "./dashboard-host.ts";
5
+ import type { DashboardHarness } from "./dashboard-host-contract.ts";
6
+ import { loadedDashboardHarnessDigest } from "./dashboard-harness.ts";
7
+ import { createDispatchBudget, openResourceBudget, resourceBindingDigest } from "./resource-budget.ts";
8
+ import { dispatchRequestDigest, type DispatchRequest } from "./dispatch-control.ts";
9
+ import { serveDashboardHost } from "./dashboard-host-transport.ts";
10
+ import type { DeclaredWorkState } from "./work-command.ts";
11
+ import type { OrdinaryChildren } from "./ordinary-children.ts";
12
+ import { dataDigest } from "./debrief-contract.ts";
13
+
14
+ export interface DailyDashboardHostInput {id:string;cwd:string;directory:string;socketPath?:string;declared:DeclaredWorkState;ordinary:OrdinaryChildren;harness:DashboardHarness;author:string}
15
+ const idPattern=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
16
+ const hash=(value:string)=>createHash("sha256").update(value).digest("hex");
17
+
18
+ /** One explicitly started, process-owned daily host. No authority, controller or prior host is recovered from disk. */
19
+ export async function startDailyDashboardHost(input:DailyDashboardHostInput){
20
+ if(!idPattern.test(input.id)||!isAbsolute(input.cwd)||resolve(input.cwd)!==input.cwd||!isAbsolute(input.directory)||resolve(input.directory)!==input.directory||!input.author.trim()||input.author.length>256)throw Error("explicit bounded daily host configuration required");
21
+ const harnessDigest=loadedDashboardHarnessDigest(input.harness);if(!harnessDigest)throw Error("loaded skill-harness bridge required");
22
+ if(input.declared.ledgerPath!==join(input.cwd,".pi","work.jsonl"))throw Error("current declared work must belong to this host project");
23
+ await mkdir(input.directory,{mode:0o700});const archiveRoot=join(input.directory,"archive");await mkdir(archiveRoot,{mode:0o700});
24
+ const reference=input.harness.retainArchiveSource(archiveRoot,{sourceId:`host-${input.id}-bootstrap`,parser:{id:"daily-host-bootstrap",version:"1"},retention:"exact",bytes:Buffer.from("explicit daily host bootstrap")});
25
+ const component={kind:"detector",id:"retained-work",version:"1",population:`daily:${input.id}`};
26
+ const trustInput={archiveRoot,component,seed:hash(`daily-host\0${input.id}`),maxUnflagged:0,cohort:[{incidentId:"bootstrap",manifestId:reference.manifestId,flagged:true,split:"calibration"}],exposure:null};
27
+ const trustPolicyId=input.harness.trustPolicyDigest(trustInput),trustDirectory=join(input.directory,"trust");input.harness.createTrustLifecycle(trustDirectory,trustInput,[trustPolicyId]);
28
+ const workRelative="work.jsonl",factsRelative="facts.jsonl",workMirror=join(input.directory,workRelative);await writeFile(workMirror,await readFile(input.declared.ledgerPath),{mode:0o600,flag:"wx"});
29
+ const refreshWork=async()=>{const temporary=`${workMirror}.${process.pid}.${randomUUID()}.tmp`;try{await writeFile(temporary,await readFile(input.declared.ledgerPath),{mode:0o600,flag:"wx"});await rename(temporary,workMirror);}finally{await rm(temporary,{force:true});}};
30
+ const facts={scopeDigest:input.declared.selectedSnapshot.snapshot.digest,version:"declared-work-v1",population:`daily:${input.id}`,expectedWaits:[],checkpoints:[],violations:[],priorAccepted:[]};await writeFile(join(input.directory,factsRelative),JSON.stringify(facts)+"\n",{mode:0o600,flag:"wx"});
31
+ const policy={version:"archive-policy-v2",id:`daily-${input.id}`,revision:"1",sourceRoot:input.directory,archiveRoot,maxBytes:8*1024*1024,retention:"exact",expiresAt:new Date(Date.now()+24*60*60*1000).toISOString(),sources:[{id:"work",path:workRelative,parser:{id:"pi-daddy-work-ledger",version:"4"},contentPolicy:"manifest-only"},{id:"facts",path:factsRelative,parser:{id:"pi-daddy-work-signal-facts",version:"1"},contentPolicy:"manifest-only"}]};
32
+ const policyPath=join(input.directory,"archive-policy.json"),policyBytes=JSON.stringify(policy);await writeFile(policyPath,policyBytes,{mode:0o600,flag:"wx"});
33
+ const authorityDigest=dataDigest({kind:"daily-dashboard-host",id:input.id,cwd:input.cwd}),budget=await createDispatchBudget({directory:join(input.directory,"budget"),authorityDigest,limits:{maxAttempts:6,maxInputBytes:65536,maxConcurrent:3}});
34
+ const config:DashboardHostConfig={version:"producer-dashboard-host-v1",trustDirectory,trustPolicyId,archiveRoot,scope:`daily:${input.id}`,author:input.author,policyPath,policySha256:hash(policyBytes),sources:[{id:"work",kind:"work"},{id:"facts",kind:"facts"}],selection:input.declared.selectedSnapshot,cases:null,blind:null,budgetDigest:resourceBindingDigest(budget),experimentDigest:null,harnessArtifactDigest:harnessDigest,ordinaryDigest:input.ordinary.bindingDigest};
35
+ const requestDigests=new Set<string>(),dispatchDigests=new Set<string>();let host:ReturnType<typeof createDashboardHost>,factsResultManifest:string|null=null;
36
+ const authority=():DashboardHostAuthority=>({hostDigests:[dashboardHostDigest(config)],requestDigests:[...requestDigests],workContext:{selectedSnapshot:config.selection,authority:null},dispatch:{authorityDigest,requestDigests:[...dispatchDigests]},experiment:null});
37
+ const request=(context:{hostDigest:string;selectionDigest:string;tip:string},operation:DashboardHostRequest["operation"],payload:unknown,requestId:string):DashboardHostRequest=>({version:"1.0",requestId,hostDigest:context.hostDigest,selectionDigest:context.selectionDigest,expectedTip:context.tip,operation,payload});
38
+ host=createDashboardHost({harness:input.harness,config,budget,ordinary:input.ordinary,authority,beforeObservation:sourceId=>sourceId==="work"?refreshWork():Promise.resolve(),humanActions:async context=>{
39
+ const state=await openResourceBudget(budget).controls(null).inspect(),action=state.paused?"resume-dispatch":"pause-dispatch",key=state.paused?"resume-dispatch":"pause-new-dispatch";
40
+ const native:DispatchRequest={version:"1.0",requestId:`${action}:${state.revision}`,bindingDigest:config.budgetDigest,expectedRevision:state.revision,action,targetExecutionId:null};dispatchDigests.add(dispatchRequestDigest(native));
41
+ const hostRequest=request(context,"dispatch",native,`host-${action}:${state.revision}`);requestDigests.add(dashboardHostRequestDigest(hostRequest));
42
+ const previous=context.observations.find(x=>x.sourceId==="work")?.checkpointId??null,refresh=request(context,"observe",{sourceId:"work",previousCheckpointId:previous,facts:context.observations.find(x=>x.sourceId==="facts")?.checkpointId?factsResultManifest:null},`refresh-work:${state.revision}:${context.tip.slice(0,12)}`);requestDigests.add(dashboardHostRequestDigest(refresh));
43
+ return [{key,label:state.paused?"Resume new governed dispatch":"Pause new governed dispatch; running children continue",request:hostRequest},{key:"refresh-current-work",label:"Capture current declared work and attempts",request:refresh}];
44
+ }});
45
+ const before=await host.frame(),observeFacts=request({hostDigest:host.hostDigest,selectionDigest:before.selectionDigest,tip:before.tip},"observe",{sourceId:"facts",previousCheckpointId:null,facts:null},"observe-current-facts");requestDigests.add(dashboardHostRequestDigest(observeFacts));const factsResult=await host.action(observeFacts) as {result:{sourceManifestId:string}};factsResultManifest=factsResult.result.sourceManifestId;
46
+ const afterFacts=await host.frame(),observeWork=request({hostDigest:host.hostDigest,selectionDigest:afterFacts.selectionDigest,tip:afterFacts.tip},"observe",{sourceId:"work",previousCheckpointId:null,facts:factsResult.result.sourceManifestId},"observe-current-work");requestDigests.add(dashboardHostRequestDigest(observeWork));await host.action(observeWork);
47
+ const socketPath=input.socketPath??join(input.directory,"dashboard.sock"),server=await serveDashboardHost(socketPath,host);
48
+ let closed=false;return Object.freeze({host,socketPath,directory:input.directory,async close(){if(closed)return;closed=true;await server.close();}});
49
+ }
@@ -0,0 +1,59 @@
1
+ import { constants } from "node:fs";
2
+ import { open, lstat } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import { createHash } from "node:crypto";
5
+ import { Compile } from "typebox/compile";
6
+ import { parseRetentionJson } from "./retention-json.ts";
7
+ import { runWithFinalizers } from "./finalization.ts";
8
+
9
+ export const ARCHIVE_PROJECTION_VERSION = "execution-archive-projection-v1" as const;
10
+ export const DAILY_INPUT_LIMIT = 4 * 1024 * 1024;
11
+ export interface ArchiveExecution {
12
+ executionId: string; parentExecutionIds: (string | null)[]; retainedSessionIds: string[]; activeBranch: null;
13
+ toolCallIds: (string | null)[]; archiveIds: string[]; runtime: "running" | "terminal" | "conflict";
14
+ outcome: { code: number | null; signal: string | null; timedOut: boolean; aborted: boolean; truncated: boolean; failed: boolean } | null;
15
+ sourceReferences: string[]; issues: string[]; coverage: "partial"; acceptance: "not-assessed";
16
+ }
17
+ export interface ArchiveProjection { version: typeof ARCHIVE_PROJECTION_VERSION; executions: ArchiveExecution[]; acceptance: "not-assessed" }
18
+ const schema = createRequire(import.meta.url)("../contracts/daily-view/v1/p03/projection.schema.json");
19
+ const validator = Compile(schema);
20
+ export const byteHash = (bytes: Uint8Array | string): string => createHash("sha256").update(bytes).digest("hex");
21
+ export function freezeDaily<T>(value: T): T {
22
+ if (value && typeof value === "object") { Object.values(value).forEach(freezeDaily); Object.freeze(value); }
23
+ return value;
24
+ }
25
+ /** Exact vendored P03 shape, plus no duplicate execution identity or contradictory terminal shape.
26
+ * Only the producer's emitted integer-token subset is supported; no lossy fractional rounding. */
27
+ export function parseArchiveProjection(text: string): ArchiveProjection {
28
+ const value = parseRetentionJson(text, DAILY_INPUT_LIMIT);
29
+ for (const match of text.matchAll(/"(?:[^"\\]|\\[\s\S])*"|[^\s{}\[\],:]+/g)) {
30
+ if (/^-?\d/.test(match[0]) && !/^-?(0|[1-9]\d*)$/.test(match[0])) throw new TypeError("unsupported archive numeric token");
31
+ }
32
+ if (!validator.Check(value)) throw new TypeError("unsupported archive projection shape/version");
33
+ const projection = value as ArchiveProjection, ids = new Set<string>();
34
+ for (const e of projection.executions) {
35
+ if (ids.has(e.executionId) || (e.runtime !== "terminal" && e.outcome !== null)) throw new TypeError("ambiguous archive execution");
36
+ ids.add(e.executionId);
37
+ }
38
+ projection.executions.sort((a, b) => a.executionId < b.executionId ? -1 : a.executionId > b.executionId ? 1 : 0);
39
+ return freezeDaily(projection);
40
+ }
41
+ export type SnapshotRead = { status: "read"; bytes: Buffer; sha256: string } | { status: "missing" | "error" | "unconfigured"; reason: string };
42
+ /** Caller-authorized exact file only. No directories, symlink following, watching or mtime freshness claim. */
43
+ export async function readDailySnapshot(path: string | undefined, limit = DAILY_INPUT_LIMIT): Promise<SnapshotRead> {
44
+ if (!path) return { status: "unconfigured", reason: "not-configured" };
45
+ try {
46
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
47
+ return await runWithFinalizers(async (): Promise<SnapshotRead> => {
48
+ const before = await file.stat({ bigint: true });
49
+ if (!before.isFile() || before.size > BigInt(limit)) return { status: "error", reason: "not-regular-or-over-limit" };
50
+ const bytes = Buffer.alloc(Number(before.size)); let offset = 0;
51
+ while (offset < bytes.length) { const n = (await file.read(bytes, offset, bytes.length - offset, offset)).bytesRead;
52
+ if (!n) return { status: "error", reason: "changed-during-read" }; offset += n; }
53
+ const after = await file.stat({ bigint: true }), located = await lstat(path, { bigint: true });
54
+ if (before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs ||
55
+ after.dev !== located.dev || after.ino !== located.ino) return { status: "error", reason: "changed-during-read" };
56
+ return { status: "read", bytes, sha256: byteHash(bytes) };
57
+ }, [{ label: "snapshot close failed", run: () => file.close() }]);
58
+ } catch (error) { return { status: (error as { code?: string }).code === "ENOENT" ? "missing" : "error", reason: "snapshot-unavailable" }; }
59
+ }
@@ -0,0 +1,44 @@
1
+ import type { DailyView } from "./daily-view.ts";
2
+ const clean = (value: unknown) => String(value ?? "unknown").replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/g, " ").trim();
3
+ /** Text-only, accessible labels. No command links, control actions, model calls or terminal input. */
4
+ export function renderDailyView(view: DailyView, width = 100): string {
5
+ const lines = ["PI-DADDY — READ-ONLY DAILY VIEW", view.narrative,
6
+ `Scope: ${view.scope ? `${view.scope.id} revision ${view.scope.revision} digest ${view.scope.digest}` : "UNRESOLVED — select an exact P01 snapshot"}`,
7
+ `Acceptance authority: ${view.authority}; coverage: PARTIAL; freshness: SNAPSHOT / UNKNOWN`,
8
+ `Models: NOT CONSULTED; connectivity UNKNOWN. Continuity: ${view.continuity}${view.scopeChanged ? "; SCOPE CHANGED" : ""}`,
9
+ `Inputs: work ${view.sources.work}; archive ${view.sources.archive}`, "", "INTENDED OBLIGATIONS"];
10
+ if (!view.obligations.length) lines.push("EMPTY / UNRESOLVED: no scoped obligations available; not proof of no intended work.");
11
+ const attempts = new Map(view.attempts.map(a => [a.executionId, a]));
12
+ for (const o of view.obligations) {
13
+ lines.push(`${o.obligation.id}@${o.obligation.revision} | ${o.acceptance.toUpperCase()} | ${o.activity.toUpperCase()}`,
14
+ ` intent ${o.intent.id}@${o.intent.revision}; policy ${o.policy.id}@${o.policy.revision}`,
15
+ ` artifact ${o.artifactCoverage}; evidence ${o.evidenceCoverage}; P01 matched receipts: ${o.receiptIds.join(", ") || "none"}`,
16
+ ` obstacle: ${o.obstacles.join(", ") || (o.acceptance === "accepted-under-supplied-authority" ? "none under supplied snapshot authority" : "acceptance not established")}`);
17
+ for (const id of o.attempts) {
18
+ const a = attempts.get(id)!;
19
+ lines.push(` attempt ${id} (logical ${a.logicalChildId ?? "unknown"}) | P01 runtime ${a.intentRuntime} | P03 ${a.archive?.runtime ?? "unavailable"}`);
20
+ }
21
+ for (const ref of o.claimReferences) lines.push(` claim source ${ref}`);
22
+ }
23
+ lines.push("", "EXECUTION SNAPSHOTS — NOT ACCEPTANCE");
24
+ if (!view.attempts.length) lines.push("EMPTY: no execution attempts in these snapshots.");
25
+ for (const a of view.attempts) {
26
+ const e = a.archive;
27
+ lines.push(`${a.executionId} | logical ${a.logicalChildId ?? "unknown"} | P01 ${a.intentRuntime} | P03 ${e?.runtime ?? "unavailable"}`);
28
+ if (e) {
29
+ lines.push(` producer-reported exit ${e.outcome?.code ?? "unknown"}; failed control/runtime ${e.outcome?.failed ?? "unknown"}; aborted ${e.outcome?.aborted ?? "unknown"}`,
30
+ ` parents ${e.parentExecutionIds.map(clean).join(", ") || "unknown"}; calls ${e.toolCallIds.map(clean).join(", ") || "unknown"}`,
31
+ ` reported retained sessions ${e.retainedSessionIds.join(", ") || "none"}; ACTIVE BRANCH UNKNOWN`,
32
+ " check receipt detail/bytes not supplied by P03 v1; never inferred from exit status");
33
+ }
34
+ lines.push(` obstacle/coverage: ${a.issues.join(", ") || "partial snapshot; freshness unknown"}`);
35
+ for (const r of a.sourceAvailability) lines.push(` source ${r.sha256} | ${r.state}`);
36
+ }
37
+ lines.push("", `Coverage gaps: ${view.issues.join("; ") || "partial snapshots; live freshness unavailable"}`,
38
+ `Work source SHA256: ${view.sources.workSha256 ?? "missing"}`, `Archive projection SHA256: ${view.sources.archiveSha256 ?? "missing"}`,
39
+ "P06: fixed digest profile only; arbitrary shell/model effects and aggregate CPU/memory/PID/money caps unsupported.",
40
+ "No steering, dispatch, acceptance, session edit or recovery action is performed by this view.");
41
+ const bounded = lines.slice(0, 300); if (lines.length > 300) bounded.push(`${lines.length - 300} additional lines omitted; full rows remain in the read-model.`);
42
+ const columns = Number.isFinite(width) ? Math.max(20, Math.min(240, Math.floor(width))) : 100;
43
+ return bounded.map(line => { const text = [...clean(line)]; return text.length > columns ? text.slice(0, columns - 3).join("") + "..." : text.join(""); }).join("\n");
44
+ }