@coool/opencode-adaptive 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/dist/adapters/codex-capacity/index.d.ts +4 -0
  4. package/dist/adapters/codex-capacity/index.js +5 -0
  5. package/dist/adapters/codex-capacity/index.js.map +1 -0
  6. package/dist/adapters/codex-capacity/parse.d.ts +28 -0
  7. package/dist/adapters/codex-capacity/parse.js +146 -0
  8. package/dist/adapters/codex-capacity/parse.js.map +1 -0
  9. package/dist/adapters/codex-capacity/reading.d.ts +9 -0
  10. package/dist/adapters/codex-capacity/reading.js +45 -0
  11. package/dist/adapters/codex-capacity/reading.js.map +1 -0
  12. package/dist/adapters/codex-capacity/sensor.d.ts +23 -0
  13. package/dist/adapters/codex-capacity/sensor.js +30 -0
  14. package/dist/adapters/codex-capacity/sensor.js.map +1 -0
  15. package/dist/adapters/codex-capacity/types.d.ts +31 -0
  16. package/dist/adapters/codex-capacity/types.js +18 -0
  17. package/dist/adapters/codex-capacity/types.js.map +1 -0
  18. package/dist/adapters/opencode/escalation-tool.d.ts +51 -0
  19. package/dist/adapters/opencode/escalation-tool.js +61 -0
  20. package/dist/adapters/opencode/escalation-tool.js.map +1 -0
  21. package/dist/adapters/opencode/events.d.ts +23 -0
  22. package/dist/adapters/opencode/events.js +68 -0
  23. package/dist/adapters/opencode/events.js.map +1 -0
  24. package/dist/adapters/opencode/hooks.d.ts +34 -0
  25. package/dist/adapters/opencode/hooks.js +29 -0
  26. package/dist/adapters/opencode/hooks.js.map +1 -0
  27. package/dist/adapters/opencode/index.d.ts +16 -0
  28. package/dist/adapters/opencode/index.js +9 -0
  29. package/dist/adapters/opencode/index.js.map +1 -0
  30. package/dist/adapters/opencode/inventory.d.ts +30 -0
  31. package/dist/adapters/opencode/inventory.js +53 -0
  32. package/dist/adapters/opencode/inventory.js.map +1 -0
  33. package/dist/adapters/opencode/report.d.ts +91 -0
  34. package/dist/adapters/opencode/report.js +145 -0
  35. package/dist/adapters/opencode/report.js.map +1 -0
  36. package/dist/adapters/opencode/session-ops.d.ts +39 -0
  37. package/dist/adapters/opencode/session-ops.js +32 -0
  38. package/dist/adapters/opencode/session-ops.js.map +1 -0
  39. package/dist/adapters/opencode/storage.d.ts +15 -0
  40. package/dist/adapters/opencode/storage.js +8 -0
  41. package/dist/adapters/opencode/storage.js.map +1 -0
  42. package/dist/adapters/opencode/tier-tool.d.ts +37 -0
  43. package/dist/adapters/opencode/tier-tool.js +72 -0
  44. package/dist/adapters/opencode/tier-tool.js.map +1 -0
  45. package/dist/adapters/opencode/types.d.ts +101 -0
  46. package/dist/adapters/opencode/types.js +2 -0
  47. package/dist/adapters/opencode/types.js.map +1 -0
  48. package/dist/core/compatibility/gate.d.ts +7 -0
  49. package/dist/core/compatibility/gate.js +51 -0
  50. package/dist/core/compatibility/gate.js.map +1 -0
  51. package/dist/core/compatibility/index.d.ts +2 -0
  52. package/dist/core/compatibility/index.js +3 -0
  53. package/dist/core/compatibility/index.js.map +1 -0
  54. package/dist/core/compatibility/types.d.ts +53 -0
  55. package/dist/core/compatibility/types.js +27 -0
  56. package/dist/core/compatibility/types.js.map +1 -0
  57. package/dist/core/scheduler/admit.d.ts +6 -0
  58. package/dist/core/scheduler/admit.js +154 -0
  59. package/dist/core/scheduler/admit.js.map +1 -0
  60. package/dist/core/scheduler/escalate.d.ts +6 -0
  61. package/dist/core/scheduler/escalate.js +45 -0
  62. package/dist/core/scheduler/escalate.js.map +1 -0
  63. package/dist/core/scheduler/index.d.ts +4 -0
  64. package/dist/core/scheduler/index.js +5 -0
  65. package/dist/core/scheduler/index.js.map +1 -0
  66. package/dist/core/scheduler/mode.d.ts +8 -0
  67. package/dist/core/scheduler/mode.js +10 -0
  68. package/dist/core/scheduler/mode.js.map +1 -0
  69. package/dist/core/scheduler/types.d.ts +136 -0
  70. package/dist/core/scheduler/types.js +75 -0
  71. package/dist/core/scheduler/types.js.map +1 -0
  72. package/dist/core/tier-policy/index.d.ts +3 -0
  73. package/dist/core/tier-policy/index.js +4 -0
  74. package/dist/core/tier-policy/index.js.map +1 -0
  75. package/dist/core/tier-policy/resolve.d.ts +18 -0
  76. package/dist/core/tier-policy/resolve.js +52 -0
  77. package/dist/core/tier-policy/resolve.js.map +1 -0
  78. package/dist/core/tier-policy/tier-list.d.ts +10 -0
  79. package/dist/core/tier-policy/tier-list.js +23 -0
  80. package/dist/core/tier-policy/tier-list.js.map +1 -0
  81. package/dist/core/tier-policy/types.d.ts +86 -0
  82. package/dist/core/tier-policy/types.js +49 -0
  83. package/dist/core/tier-policy/types.js.map +1 -0
  84. package/dist/core/work-unit/index.d.ts +3 -0
  85. package/dist/core/work-unit/index.js +4 -0
  86. package/dist/core/work-unit/index.js.map +1 -0
  87. package/dist/core/work-unit/interruption.d.ts +7 -0
  88. package/dist/core/work-unit/interruption.js +12 -0
  89. package/dist/core/work-unit/interruption.js.map +1 -0
  90. package/dist/core/work-unit/tracker.d.ts +53 -0
  91. package/dist/core/work-unit/tracker.js +156 -0
  92. package/dist/core/work-unit/tracker.js.map +1 -0
  93. package/dist/core/work-unit/types.d.ts +151 -0
  94. package/dist/core/work-unit/types.js +23 -0
  95. package/dist/core/work-unit/types.js.map +1 -0
  96. package/dist/execution/continuation/execute.d.ts +18 -0
  97. package/dist/execution/continuation/execute.js +31 -0
  98. package/dist/execution/continuation/execute.js.map +1 -0
  99. package/dist/execution/continuation/index.d.ts +4 -0
  100. package/dist/execution/continuation/index.js +4 -0
  101. package/dist/execution/continuation/index.js.map +1 -0
  102. package/dist/execution/continuation/plan.d.ts +12 -0
  103. package/dist/execution/continuation/plan.js +18 -0
  104. package/dist/execution/continuation/plan.js.map +1 -0
  105. package/dist/execution/continuation/prompts.d.ts +7 -0
  106. package/dist/execution/continuation/prompts.js +8 -0
  107. package/dist/execution/continuation/prompts.js.map +1 -0
  108. package/dist/execution/continuation/types.d.ts +68 -0
  109. package/dist/execution/continuation/types.js +15 -0
  110. package/dist/execution/continuation/types.js.map +1 -0
  111. package/dist/execution/escalation/index.d.ts +1 -0
  112. package/dist/execution/escalation/index.js +2 -0
  113. package/dist/execution/escalation/index.js.map +1 -0
  114. package/dist/execution/escalation/types.d.ts +30 -0
  115. package/dist/execution/escalation/types.js +49 -0
  116. package/dist/execution/escalation/types.js.map +1 -0
  117. package/dist/execution/index.d.ts +4 -0
  118. package/dist/execution/index.js +5 -0
  119. package/dist/execution/index.js.map +1 -0
  120. package/dist/execution/parking/index.d.ts +2 -0
  121. package/dist/execution/parking/index.js +2 -0
  122. package/dist/execution/parking/index.js.map +1 -0
  123. package/dist/execution/parking/plan.d.ts +39 -0
  124. package/dist/execution/parking/plan.js +34 -0
  125. package/dist/execution/parking/plan.js.map +1 -0
  126. package/dist/execution/retry/decide.d.ts +29 -0
  127. package/dist/execution/retry/decide.js +75 -0
  128. package/dist/execution/retry/decide.js.map +1 -0
  129. package/dist/execution/retry/index.d.ts +2 -0
  130. package/dist/execution/retry/index.js +3 -0
  131. package/dist/execution/retry/index.js.map +1 -0
  132. package/dist/execution/retry/types.d.ts +64 -0
  133. package/dist/execution/retry/types.js +9 -0
  134. package/dist/execution/retry/types.js.map +1 -0
  135. package/dist/gui-experimental.cjs +1 -0
  136. package/dist/gui.d.ts +16 -0
  137. package/dist/gui.js +73 -0
  138. package/dist/gui.js.map +1 -0
  139. package/dist/health/capacity/index.d.ts +3 -0
  140. package/dist/health/capacity/index.js +4 -0
  141. package/dist/health/capacity/index.js.map +1 -0
  142. package/dist/health/capacity/merge.d.ts +57 -0
  143. package/dist/health/capacity/merge.js +154 -0
  144. package/dist/health/capacity/merge.js.map +1 -0
  145. package/dist/health/capacity/registry.d.ts +40 -0
  146. package/dist/health/capacity/registry.js +106 -0
  147. package/dist/health/capacity/registry.js.map +1 -0
  148. package/dist/health/capacity/types.d.ts +73 -0
  149. package/dist/health/capacity/types.js +6 -0
  150. package/dist/health/capacity/types.js.map +1 -0
  151. package/dist/health/circuit-breaker/breaker.d.ts +91 -0
  152. package/dist/health/circuit-breaker/breaker.js +150 -0
  153. package/dist/health/circuit-breaker/breaker.js.map +1 -0
  154. package/dist/health/circuit-breaker/index.d.ts +2 -0
  155. package/dist/health/circuit-breaker/index.js +3 -0
  156. package/dist/health/circuit-breaker/index.js.map +1 -0
  157. package/dist/health/circuit-breaker/types.d.ts +52 -0
  158. package/dist/health/circuit-breaker/types.js +13 -0
  159. package/dist/health/circuit-breaker/types.js.map +1 -0
  160. package/dist/health/classifier/classify.d.ts +8 -0
  161. package/dist/health/classifier/classify.js +245 -0
  162. package/dist/health/classifier/classify.js.map +1 -0
  163. package/dist/health/classifier/index.d.ts +2 -0
  164. package/dist/health/classifier/index.js +3 -0
  165. package/dist/health/classifier/index.js.map +1 -0
  166. package/dist/health/classifier/types.d.ts +71 -0
  167. package/dist/health/classifier/types.js +27 -0
  168. package/dist/health/classifier/types.js.map +1 -0
  169. package/dist/health/index.d.ts +4 -0
  170. package/dist/health/index.js +5 -0
  171. package/dist/health/index.js.map +1 -0
  172. package/dist/health/resource-health/index.d.ts +4 -0
  173. package/dist/health/resource-health/index.js +5 -0
  174. package/dist/health/resource-health/index.js.map +1 -0
  175. package/dist/health/resource-health/key.d.ts +18 -0
  176. package/dist/health/resource-health/key.js +54 -0
  177. package/dist/health/resource-health/key.js.map +1 -0
  178. package/dist/health/resource-health/registry.d.ts +46 -0
  179. package/dist/health/resource-health/registry.js +276 -0
  180. package/dist/health/resource-health/registry.js.map +1 -0
  181. package/dist/health/resource-health/transitions.d.ts +9 -0
  182. package/dist/health/resource-health/transitions.js +157 -0
  183. package/dist/health/resource-health/transitions.js.map +1 -0
  184. package/dist/health/resource-health/types.d.ts +122 -0
  185. package/dist/health/resource-health/types.js +44 -0
  186. package/dist/health/resource-health/types.js.map +1 -0
  187. package/dist/index.d.ts +11 -0
  188. package/dist/index.js +105 -0
  189. package/dist/index.js.map +1 -0
  190. package/dist/plugin-id.d.ts +2 -0
  191. package/dist/plugin-id.js +3 -0
  192. package/dist/plugin-id.js.map +1 -0
  193. package/dist/rpc/definition.d.ts +123 -0
  194. package/dist/rpc/definition.js +130 -0
  195. package/dist/rpc/definition.js.map +1 -0
  196. package/dist/rpc/handlers.d.ts +53 -0
  197. package/dist/rpc/handlers.js +125 -0
  198. package/dist/rpc/handlers.js.map +1 -0
  199. package/dist/rpc/status.d.ts +42 -0
  200. package/dist/rpc/status.js +45 -0
  201. package/dist/rpc/status.js.map +1 -0
  202. package/dist/rpc.d.ts +10 -0
  203. package/dist/rpc.js +8 -0
  204. package/dist/rpc.js.map +1 -0
  205. package/dist/state/capacity/index.d.ts +3 -0
  206. package/dist/state/capacity/index.js +4 -0
  207. package/dist/state/capacity/index.js.map +1 -0
  208. package/dist/state/capacity/parse.d.ts +2 -0
  209. package/dist/state/capacity/parse.js +122 -0
  210. package/dist/state/capacity/parse.js.map +1 -0
  211. package/dist/state/capacity/store.d.ts +18 -0
  212. package/dist/state/capacity/store.js +78 -0
  213. package/dist/state/capacity/store.js.map +1 -0
  214. package/dist/state/capacity/types.d.ts +37 -0
  215. package/dist/state/capacity/types.js +11 -0
  216. package/dist/state/capacity/types.js.map +1 -0
  217. package/dist/state/health/index.d.ts +4 -0
  218. package/dist/state/health/index.js +4 -0
  219. package/dist/state/health/index.js.map +1 -0
  220. package/dist/state/health/parse.d.ts +2 -0
  221. package/dist/state/health/parse.js +137 -0
  222. package/dist/state/health/parse.js.map +1 -0
  223. package/dist/state/health/store.d.ts +17 -0
  224. package/dist/state/health/store.js +69 -0
  225. package/dist/state/health/store.js.map +1 -0
  226. package/dist/state/health/types.d.ts +23 -0
  227. package/dist/state/health/types.js +5 -0
  228. package/dist/state/health/types.js.map +1 -0
  229. package/dist/state/parked-work/index.d.ts +4 -0
  230. package/dist/state/parked-work/index.js +4 -0
  231. package/dist/state/parked-work/index.js.map +1 -0
  232. package/dist/state/parked-work/parse.d.ts +6 -0
  233. package/dist/state/parked-work/parse.js +83 -0
  234. package/dist/state/parked-work/parse.js.map +1 -0
  235. package/dist/state/parked-work/store.d.ts +17 -0
  236. package/dist/state/parked-work/store.js +98 -0
  237. package/dist/state/parked-work/store.js.map +1 -0
  238. package/dist/state/parked-work/types.d.ts +41 -0
  239. package/dist/state/parked-work/types.js +7 -0
  240. package/dist/state/parked-work/types.js.map +1 -0
  241. package/dist/state/receipts/bound.d.ts +14 -0
  242. package/dist/state/receipts/bound.js +77 -0
  243. package/dist/state/receipts/bound.js.map +1 -0
  244. package/dist/state/receipts/index.d.ts +6 -0
  245. package/dist/state/receipts/index.js +5 -0
  246. package/dist/state/receipts/index.js.map +1 -0
  247. package/dist/state/receipts/parse.d.ts +6 -0
  248. package/dist/state/receipts/parse.js +226 -0
  249. package/dist/state/receipts/parse.js.map +1 -0
  250. package/dist/state/receipts/store.d.ts +34 -0
  251. package/dist/state/receipts/store.js +148 -0
  252. package/dist/state/receipts/store.js.map +1 -0
  253. package/dist/state/receipts/types.d.ts +61 -0
  254. package/dist/state/receipts/types.js +12 -0
  255. package/dist/state/receipts/types.js.map +1 -0
  256. package/dist/state/session-modes/index.d.ts +4 -0
  257. package/dist/state/session-modes/index.js +4 -0
  258. package/dist/state/session-modes/index.js.map +1 -0
  259. package/dist/state/session-modes/parse.d.ts +8 -0
  260. package/dist/state/session-modes/parse.js +62 -0
  261. package/dist/state/session-modes/parse.js.map +1 -0
  262. package/dist/state/session-modes/store.d.ts +20 -0
  263. package/dist/state/session-modes/store.js +87 -0
  264. package/dist/state/session-modes/store.js.map +1 -0
  265. package/dist/state/session-modes/types.d.ts +41 -0
  266. package/dist/state/session-modes/types.js +7 -0
  267. package/dist/state/session-modes/types.js.map +1 -0
  268. package/dist/state/settings/index.d.ts +4 -0
  269. package/dist/state/settings/index.js +4 -0
  270. package/dist/state/settings/index.js.map +1 -0
  271. package/dist/state/settings/parse.d.ts +33 -0
  272. package/dist/state/settings/parse.js +261 -0
  273. package/dist/state/settings/parse.js.map +1 -0
  274. package/dist/state/settings/store.d.ts +21 -0
  275. package/dist/state/settings/store.js +65 -0
  276. package/dist/state/settings/store.js.map +1 -0
  277. package/dist/state/settings/types.d.ts +62 -0
  278. package/dist/state/settings/types.js +12 -0
  279. package/dist/state/settings/types.js.map +1 -0
  280. package/dist/state/tier-requests/index.d.ts +4 -0
  281. package/dist/state/tier-requests/index.js +4 -0
  282. package/dist/state/tier-requests/index.js.map +1 -0
  283. package/dist/state/tier-requests/parse.d.ts +3 -0
  284. package/dist/state/tier-requests/parse.js +53 -0
  285. package/dist/state/tier-requests/parse.js.map +1 -0
  286. package/dist/state/tier-requests/store.d.ts +27 -0
  287. package/dist/state/tier-requests/store.js +96 -0
  288. package/dist/state/tier-requests/store.js.map +1 -0
  289. package/dist/state/tier-requests/types.d.ts +30 -0
  290. package/dist/state/tier-requests/types.js +7 -0
  291. package/dist/state/tier-requests/types.js.map +1 -0
  292. package/dist/supervisor/index.d.ts +4 -0
  293. package/dist/supervisor/index.js +4 -0
  294. package/dist/supervisor/index.js.map +1 -0
  295. package/dist/supervisor/log.d.ts +10 -0
  296. package/dist/supervisor/log.js +14 -0
  297. package/dist/supervisor/log.js.map +1 -0
  298. package/dist/supervisor/supervisor.d.ts +86 -0
  299. package/dist/supervisor/supervisor.js +1629 -0
  300. package/dist/supervisor/supervisor.js.map +1 -0
  301. package/dist/supervisor/types.d.ts +49 -0
  302. package/dist/supervisor/types.js +3 -0
  303. package/dist/supervisor/types.js.map +1 -0
  304. package/dist/supervisor/view.d.ts +3 -0
  305. package/dist/supervisor/view.js +4 -0
  306. package/dist/supervisor/view.js.map +1 -0
  307. package/dist/tui.d.ts +15 -0
  308. package/dist/tui.js +256 -0
  309. package/dist/tui.js.map +1 -0
  310. package/dist/ui/gui/api.d.ts +17 -0
  311. package/dist/ui/gui/api.js +21 -0
  312. package/dist/ui/gui/api.js.map +1 -0
  313. package/dist/ui/gui/view.d.ts +26 -0
  314. package/dist/ui/gui/view.js +49 -0
  315. package/dist/ui/gui/view.js.map +1 -0
  316. package/dist/ui/tui/controller.d.ts +97 -0
  317. package/dist/ui/tui/controller.js +415 -0
  318. package/dist/ui/tui/controller.js.map +1 -0
  319. package/dist/ui/tui/menu.d.ts +55 -0
  320. package/dist/ui/tui/menu.js +176 -0
  321. package/dist/ui/tui/menu.js.map +1 -0
  322. package/dist/ui/tui/options.d.ts +19 -0
  323. package/dist/ui/tui/options.js +17 -0
  324. package/dist/ui/tui/options.js.map +1 -0
  325. package/dist/ui/tui/receipts.d.ts +21 -0
  326. package/dist/ui/tui/receipts.js +112 -0
  327. package/dist/ui/tui/receipts.js.map +1 -0
  328. package/dist/ui/tui/status.d.ts +22 -0
  329. package/dist/ui/tui/status.js +92 -0
  330. package/dist/ui/tui/status.js.map +1 -0
  331. package/dist/ui/tui/tiers.d.ts +51 -0
  332. package/dist/ui/tui/tiers.js +108 -0
  333. package/dist/ui/tui/tiers.js.map +1 -0
  334. package/package.json +85 -4
@@ -0,0 +1,77 @@
1
+ import { MAX_RECEIPT_TEXT } from "./types.js";
2
+ export function boundText(raw) {
3
+ return raw.length > MAX_RECEIPT_TEXT ? raw.slice(0, MAX_RECEIPT_TEXT) : raw;
4
+ }
5
+ function boundRef(ref) {
6
+ return {
7
+ providerID: boundText(ref.providerID),
8
+ id: boundText(ref.id),
9
+ ...(ref.variant !== undefined ? { variant: boundText(ref.variant) } : {}),
10
+ };
11
+ }
12
+ function boundApply(decision) {
13
+ return {
14
+ kind: "apply",
15
+ model: boundRef(decision.model),
16
+ ...(decision.tier !== undefined ? { tier: decision.tier } : {}),
17
+ reason: decision.reason,
18
+ evidence: {
19
+ skipped: decision.evidence.skipped.map((skip) => ({
20
+ ref: boundRef(skip.ref),
21
+ why: skip.why,
22
+ })),
23
+ ...(decision.evidence.probeId !== undefined
24
+ ? { probeId: boundText(decision.evidence.probeId) }
25
+ : {}),
26
+ },
27
+ };
28
+ }
29
+ function boundDefer(decision) {
30
+ return {
31
+ kind: "defer",
32
+ reason: decision.reason,
33
+ ...(decision.detail !== undefined ? { detail: boundText(decision.detail) } : {}),
34
+ };
35
+ }
36
+ function boundPark(decision) {
37
+ return {
38
+ kind: "park",
39
+ reason: decision.reason,
40
+ ...(decision.detail !== undefined ? { detail: boundText(decision.detail) } : {}),
41
+ };
42
+ }
43
+ function boundEscalate(decision) {
44
+ return {
45
+ kind: "escalate",
46
+ target: boundRef(decision.target),
47
+ step: decision.step,
48
+ tier: decision.tier,
49
+ evidence: decision.evidence.map((item) => ({
50
+ kind: item.kind,
51
+ ...(item.detail !== undefined ? { detail: boundText(item.detail) } : {}),
52
+ })),
53
+ budgetRemaining: decision.budgetRemaining,
54
+ };
55
+ }
56
+ function boundObserveOnly(decision, depth) {
57
+ return { kind: "observe-only", would: boundDecision(decision.would, depth + 1) };
58
+ }
59
+ /**
60
+ * Structural copy of a decision with every string bounded. `depth` guards
61
+ * the `observe-only` recursion (max one nesting level is ever produced).
62
+ */
63
+ export function boundDecision(decision, depth = 0) {
64
+ switch (decision.kind) {
65
+ case "apply":
66
+ return boundApply(decision);
67
+ case "defer":
68
+ return boundDefer(decision);
69
+ case "park":
70
+ return boundPark(decision);
71
+ case "escalate":
72
+ return boundEscalate(decision);
73
+ case "observe-only":
74
+ return depth < 2 ? boundObserveOnly(decision, depth) : decision;
75
+ }
76
+ }
77
+ //# sourceMappingURL=bound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bound.js","sourceRoot":"","sources":["../../../src/state/receipts/bound.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bounding for receipts (ADP-030). Receipts are built from typed scheduler\n * decisions, but every string they can carry is truncated to\n * `MAX_RECEIPT_TEXT` before persisting — including identifiers. Boundedness\n * is what makes the no-secrets invariant checkable: no field accepts\n * unbounded text, so nothing unbounded can be persisted.\n */\nimport type {\n ApplyDecision,\n DeferDecision,\n Decision,\n EscalateDecision,\n ObserveOnlyDecision,\n ParkDecision,\n} from \"../../core/scheduler/types.js\"\nimport type { ModelRef } from \"../../core/work-unit/types.js\"\nimport { MAX_RECEIPT_TEXT } from \"./types.js\"\n\nexport function boundText(raw: string): string {\n return raw.length > MAX_RECEIPT_TEXT ? raw.slice(0, MAX_RECEIPT_TEXT) : raw\n}\n\nfunction boundRef(ref: ModelRef): ModelRef {\n return {\n providerID: boundText(ref.providerID),\n id: boundText(ref.id),\n ...(ref.variant !== undefined ? { variant: boundText(ref.variant) } : {}),\n }\n}\n\nfunction boundApply(decision: ApplyDecision): ApplyDecision {\n return {\n kind: \"apply\",\n model: boundRef(decision.model),\n ...(decision.tier !== undefined ? { tier: decision.tier } : {}),\n reason: decision.reason,\n evidence: {\n skipped: decision.evidence.skipped.map((skip) => ({\n ref: boundRef(skip.ref),\n why: skip.why,\n })),\n ...(decision.evidence.probeId !== undefined\n ? { probeId: boundText(decision.evidence.probeId) }\n : {}),\n },\n }\n}\n\nfunction boundDefer(decision: DeferDecision): DeferDecision {\n return {\n kind: \"defer\",\n reason: decision.reason,\n ...(decision.detail !== undefined ? { detail: boundText(decision.detail) } : {}),\n }\n}\n\nfunction boundPark(decision: ParkDecision): ParkDecision {\n return {\n kind: \"park\",\n reason: decision.reason,\n ...(decision.detail !== undefined ? { detail: boundText(decision.detail) } : {}),\n }\n}\n\nfunction boundEscalate(decision: EscalateDecision): EscalateDecision {\n return {\n kind: \"escalate\",\n target: boundRef(decision.target),\n step: decision.step,\n tier: decision.tier,\n evidence: decision.evidence.map((item) => ({\n kind: item.kind,\n ...(item.detail !== undefined ? { detail: boundText(item.detail) } : {}),\n })),\n budgetRemaining: decision.budgetRemaining,\n }\n}\n\nfunction boundObserveOnly(decision: ObserveOnlyDecision, depth: number): ObserveOnlyDecision {\n return { kind: \"observe-only\", would: boundDecision(decision.would, depth + 1) }\n}\n\n/**\n * Structural copy of a decision with every string bounded. `depth` guards\n * the `observe-only` recursion (max one nesting level is ever produced).\n */\nexport function boundDecision(decision: Decision, depth = 0): Decision {\n switch (decision.kind) {\n case \"apply\":\n return boundApply(decision)\n case \"defer\":\n return boundDefer(decision)\n case \"park\":\n return boundPark(decision)\n case \"escalate\":\n return boundEscalate(decision)\n case \"observe-only\":\n return depth < 2 ? boundObserveOnly(decision, depth) : decision\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export { boundDecision, boundText } from "./bound.js";
2
+ export { parseReceiptsPayload } from "./parse.js";
3
+ export { ReceiptStore } from "./store.js";
4
+ export type { ReceiptRef } from "./store.js";
5
+ export { MAX_RECEIPTS, MAX_RECEIPT_TEXT, RECEIPTS_KEY, RECEIPTS_VERSION } from "./types.js";
6
+ export type { Receipt, ReceiptInput, ReceiptMeta, ReceiptOutcome, ReceiptsPayload, ReceiptsStorage, } from "./types.js";
@@ -0,0 +1,5 @@
1
+ export { boundDecision, boundText } from "./bound.js";
2
+ export { parseReceiptsPayload } from "./parse.js";
3
+ export { ReceiptStore } from "./store.js";
4
+ export { MAX_RECEIPTS, MAX_RECEIPT_TEXT, RECEIPTS_KEY, RECEIPTS_VERSION } from "./types.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/state/receipts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { boundDecision, boundText } from \"./bound.js\"\nexport { parseReceiptsPayload } from \"./parse.js\"\nexport { ReceiptStore } from \"./store.js\"\nexport type { ReceiptRef } from \"./store.js\"\nexport { MAX_RECEIPTS, MAX_RECEIPT_TEXT, RECEIPTS_KEY, RECEIPTS_VERSION } from \"./types.js\"\nexport type {\n Receipt,\n ReceiptInput,\n ReceiptMeta,\n ReceiptOutcome,\n ReceiptsPayload,\n ReceiptsStorage,\n} from \"./types.js\"\n"]}
@@ -0,0 +1,6 @@
1
+ import type { Receipt } from "./types.js";
2
+ /**
3
+ * Parse a persisted receipts payload. Accepts the versioned shape and a
4
+ * versionless draft array; anything else yields no receipts.
5
+ */
6
+ export declare function parseReceiptsPayload(raw: unknown): Receipt[];
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Read-side parsing for persisted receipts. Every parser is total: it accepts
3
+ * `unknown` and returns typed records, dropping anything invalid — a
4
+ * corrupted receipts file can never take the plugin down (ADP-031). Reason
5
+ * vocabularies are membership-checked (the write path is typed); free-text
6
+ * fields are re-bounded. An older plugin reading newer receipts may drop
7
+ * unknown-vocabulary entries — receipts are bounded diagnostics, eviction is
8
+ * expected behavior, and the payload version guards the schema itself.
9
+ */
10
+ import { APPLY_REASONS, ESCALATION_EVIDENCE_KINDS, ESCALATION_STEPS, PARK_REASONS, SKIP_REASONS, SCHEDULER_DEFER_REASONS, } from "../../core/scheduler/types.js";
11
+ import { TIERS } from "../../core/tier-policy/types.js";
12
+ import { INTERRUPT_REASONS } from "../../core/work-unit/interruption.js";
13
+ import { FAILURE_CATEGORIES } from "../../health/classifier/types.js";
14
+ import { parseModelRef } from "../settings/parse.js";
15
+ import { MAX_RECEIPTS, MAX_RECEIPT_TEXT, RECEIPTS_VERSION } from "./types.js";
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ function boundString(raw) {
20
+ if (typeof raw !== "string")
21
+ return undefined;
22
+ return raw.length > MAX_RECEIPT_TEXT ? raw.slice(0, MAX_RECEIPT_TEXT) : raw;
23
+ }
24
+ function isFiniteNonNegative(raw) {
25
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0;
26
+ }
27
+ function parseVocab(raw, vocabulary) {
28
+ if (typeof raw !== "string")
29
+ return undefined;
30
+ return vocabulary.find((entry) => entry === raw);
31
+ }
32
+ function parseTier(raw) {
33
+ if (typeof raw !== "string")
34
+ return undefined;
35
+ return TIERS.find((tier) => tier === raw);
36
+ }
37
+ function parseOptionalBounded(raw) {
38
+ const value = boundString(raw);
39
+ return value === "" ? undefined : value;
40
+ }
41
+ /** Free-text detail keeps empty strings (round-trips through the store). */
42
+ function parseDetailBounded(raw) {
43
+ return boundString(raw) ?? undefined;
44
+ }
45
+ function parseApplyEvidence(raw) {
46
+ if (!isRecord(raw))
47
+ return undefined;
48
+ const skippedRaw = raw["skipped"];
49
+ if (!Array.isArray(skippedRaw))
50
+ return undefined;
51
+ const skipped = [];
52
+ for (const entry of skippedRaw.slice(0, 8)) {
53
+ if (!isRecord(entry))
54
+ continue;
55
+ const ref = parseModelRef(entry["ref"]);
56
+ const why = parseVocab(entry["why"], SKIP_REASONS);
57
+ if (ref === undefined || why === undefined)
58
+ continue;
59
+ skipped.push({ ref, why });
60
+ }
61
+ const probeId = parseDetailBounded(raw["probeId"]);
62
+ return { skipped, ...(probeId !== undefined ? { probeId } : {}) };
63
+ }
64
+ function parseApply(raw) {
65
+ const model = parseModelRef(raw["model"]);
66
+ const reason = parseVocab(raw["reason"], APPLY_REASONS);
67
+ const evidence = parseApplyEvidence(raw["evidence"]);
68
+ if (model === undefined || reason === undefined || evidence === undefined) {
69
+ return undefined;
70
+ }
71
+ const tier = parseTier(raw["tier"]);
72
+ return {
73
+ kind: "apply",
74
+ model,
75
+ ...(tier !== undefined ? { tier } : {}),
76
+ reason,
77
+ evidence,
78
+ };
79
+ }
80
+ function parseDefer(raw) {
81
+ const reason = parseVocab(raw["reason"], SCHEDULER_DEFER_REASONS);
82
+ if (reason === undefined)
83
+ return undefined;
84
+ const detail = parseDetailBounded(raw["detail"]);
85
+ return { kind: "defer", reason, ...(detail !== undefined ? { detail } : {}) };
86
+ }
87
+ function parsePark(raw) {
88
+ const reason = parseVocab(raw["reason"], PARK_REASONS);
89
+ if (reason === undefined)
90
+ return undefined;
91
+ const detail = parseDetailBounded(raw["detail"]);
92
+ return { kind: "park", reason, ...(detail !== undefined ? { detail } : {}) };
93
+ }
94
+ function parseEscalate(raw) {
95
+ const target = parseModelRef(raw["target"]);
96
+ const step = parseVocab(raw["step"], ESCALATION_STEPS);
97
+ const tier = parseTier(raw["tier"]);
98
+ const budgetRemainingRaw = raw["budgetRemaining"];
99
+ if (target === undefined ||
100
+ step === undefined ||
101
+ tier === undefined ||
102
+ !isFiniteNonNegative(budgetRemainingRaw)) {
103
+ return undefined;
104
+ }
105
+ const evidenceRaw = raw["evidence"];
106
+ if (!Array.isArray(evidenceRaw))
107
+ return undefined;
108
+ const evidence = [];
109
+ for (const entry of evidenceRaw.slice(0, 4)) {
110
+ if (!isRecord(entry))
111
+ continue;
112
+ const kind = parseVocab(entry["kind"], ESCALATION_EVIDENCE_KINDS);
113
+ if (kind === undefined)
114
+ continue;
115
+ const detail = parseDetailBounded(entry["detail"]);
116
+ evidence.push({ kind, ...(detail !== undefined ? { detail } : {}) });
117
+ }
118
+ return {
119
+ kind: "escalate",
120
+ target,
121
+ step,
122
+ tier,
123
+ evidence,
124
+ budgetRemaining: budgetRemainingRaw,
125
+ };
126
+ }
127
+ /** Max `observe-only` nesting accepted on read (the scheduler produces ≤ 1). */
128
+ const MAX_PARSE_DEPTH = 2;
129
+ function parseObserveOnly(raw, depth) {
130
+ if (depth >= MAX_PARSE_DEPTH)
131
+ return undefined;
132
+ const would = parseDecision(raw["would"], depth + 1);
133
+ return would === undefined ? undefined : { kind: "observe-only", would };
134
+ }
135
+ function parseDecision(raw, depth = 0) {
136
+ if (!isRecord(raw))
137
+ return undefined;
138
+ switch (raw["kind"]) {
139
+ case "apply":
140
+ return parseApply(raw);
141
+ case "defer":
142
+ return parseDefer(raw);
143
+ case "park":
144
+ return parsePark(raw);
145
+ case "escalate":
146
+ return parseEscalate(raw);
147
+ case "observe-only":
148
+ return parseObserveOnly(raw, depth);
149
+ default:
150
+ return undefined;
151
+ }
152
+ }
153
+ function parseOutcome(raw) {
154
+ if (!isRecord(raw))
155
+ return undefined;
156
+ switch (raw["status"]) {
157
+ case "succeeded":
158
+ return { status: "succeeded" };
159
+ case "failed": {
160
+ const category = parseVocab(raw["category"], FAILURE_CATEGORIES);
161
+ return category === undefined ? undefined : { status: "failed", category };
162
+ }
163
+ case "interrupted": {
164
+ const reason = parseVocab(raw["reason"], INTERRUPT_REASONS);
165
+ return reason === undefined ? undefined : { status: "interrupted", reason };
166
+ }
167
+ default:
168
+ return undefined;
169
+ }
170
+ }
171
+ function parseReceipt(raw) {
172
+ if (!isRecord(raw))
173
+ return undefined;
174
+ const id = raw["id"];
175
+ const at = raw["at"];
176
+ const sessionID = parseOptionalBounded(raw["sessionID"]);
177
+ const decision = parseDecision(raw["decision"]);
178
+ if (!(isFiniteNonNegative(id) && Number.isInteger(id) && isFiniteNonNegative(at)) ||
179
+ sessionID === undefined ||
180
+ decision === undefined) {
181
+ return undefined;
182
+ }
183
+ const unitID = parseOptionalBounded(raw["unitID"]);
184
+ const tier = parseTier(raw["tier"]);
185
+ // A present-but-invalid outcome invalidates the whole record: stripping
186
+ // just the outcome would break first-outcome-wins across a restart (the
187
+ // receipt would accept a later, different outcome as its first).
188
+ let outcome;
189
+ if (raw["outcome"] !== undefined) {
190
+ outcome = parseOutcome(raw["outcome"]);
191
+ if (outcome === undefined)
192
+ return undefined;
193
+ }
194
+ return {
195
+ id,
196
+ at,
197
+ sessionID,
198
+ ...(unitID !== undefined ? { unitID } : {}),
199
+ ...(tier !== undefined ? { tier } : {}),
200
+ decision,
201
+ ...(outcome !== undefined ? { outcome } : {}),
202
+ };
203
+ }
204
+ /**
205
+ * Parse a persisted receipts payload. Accepts the versioned shape and a
206
+ * versionless draft array; anything else yields no receipts.
207
+ */
208
+ export function parseReceiptsPayload(raw) {
209
+ let list;
210
+ if (isRecord(raw)) {
211
+ const version = raw["version"];
212
+ if (version !== RECEIPTS_VERSION && version !== undefined)
213
+ return [];
214
+ list = raw["receipts"];
215
+ }
216
+ else if (Array.isArray(raw)) {
217
+ list = raw;
218
+ }
219
+ if (!Array.isArray(list))
220
+ return [];
221
+ const parsed = list
222
+ .map(parseReceipt)
223
+ .filter((receipt) => receipt !== undefined);
224
+ return parsed.length > MAX_RECEIPTS ? parsed.slice(-MAX_RECEIPTS) : parsed;
225
+ }
226
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/state/receipts/parse.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-side parsing for persisted receipts. Every parser is total: it accepts\n * `unknown` and returns typed records, dropping anything invalid — a\n * corrupted receipts file can never take the plugin down (ADP-031). Reason\n * vocabularies are membership-checked (the write path is typed); free-text\n * fields are re-bounded. An older plugin reading newer receipts may drop\n * unknown-vocabulary entries — receipts are bounded diagnostics, eviction is\n * expected behavior, and the payload version guards the schema itself.\n */\nimport {\n APPLY_REASONS,\n ESCALATION_EVIDENCE_KINDS,\n ESCALATION_STEPS,\n PARK_REASONS,\n SKIP_REASONS,\n SCHEDULER_DEFER_REASONS,\n} from \"../../core/scheduler/types.js\"\nimport type {\n ApplyDecision,\n DeferDecision,\n Decision,\n EscalateDecision,\n EscalationEvidence,\n ObserveOnlyDecision,\n ParkDecision,\n SkippedCandidate,\n} from \"../../core/scheduler/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport { INTERRUPT_REASONS } from \"../../core/work-unit/interruption.js\"\nimport { FAILURE_CATEGORIES } from \"../../health/classifier/types.js\"\nimport { parseModelRef } from \"../settings/parse.js\"\nimport { MAX_RECEIPTS, MAX_RECEIPT_TEXT, RECEIPTS_VERSION } from \"./types.js\"\nimport type { Receipt, ReceiptOutcome } from \"./types.js\"\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction boundString(raw: unknown): string | undefined {\n if (typeof raw !== \"string\") return undefined\n return raw.length > MAX_RECEIPT_TEXT ? raw.slice(0, MAX_RECEIPT_TEXT) : raw\n}\n\nfunction isFiniteNonNegative(raw: unknown): raw is number {\n return typeof raw === \"number\" && Number.isFinite(raw) && raw >= 0\n}\n\nfunction parseVocab<V extends readonly string[]>(\n raw: unknown,\n vocabulary: V,\n): V[number] | undefined {\n if (typeof raw !== \"string\") return undefined\n return vocabulary.find((entry) => entry === raw)\n}\n\nfunction parseTier(raw: unknown): TierName | undefined {\n if (typeof raw !== \"string\") return undefined\n return TIERS.find((tier) => tier === raw)\n}\n\nfunction parseOptionalBounded(raw: unknown): string | undefined {\n const value = boundString(raw)\n return value === \"\" ? undefined : value\n}\n\n/** Free-text detail keeps empty strings (round-trips through the store). */\nfunction parseDetailBounded(raw: unknown): string | undefined {\n return boundString(raw) ?? undefined\n}\n\nfunction parseApplyEvidence(raw: unknown): ApplyDecision[\"evidence\"] | undefined {\n if (!isRecord(raw)) return undefined\n const skippedRaw = raw[\"skipped\"]\n if (!Array.isArray(skippedRaw)) return undefined\n const skipped: SkippedCandidate[] = []\n for (const entry of skippedRaw.slice(0, 8)) {\n if (!isRecord(entry)) continue\n const ref = parseModelRef(entry[\"ref\"])\n const why = parseVocab(entry[\"why\"], SKIP_REASONS)\n if (ref === undefined || why === undefined) continue\n skipped.push({ ref, why })\n }\n const probeId = parseDetailBounded(raw[\"probeId\"])\n return { skipped, ...(probeId !== undefined ? { probeId } : {}) }\n}\n\nfunction parseApply(raw: Record<string, unknown>): ApplyDecision | undefined {\n const model = parseModelRef(raw[\"model\"])\n const reason = parseVocab(raw[\"reason\"], APPLY_REASONS)\n const evidence = parseApplyEvidence(raw[\"evidence\"])\n if (model === undefined || reason === undefined || evidence === undefined) {\n return undefined\n }\n const tier = parseTier(raw[\"tier\"])\n return {\n kind: \"apply\",\n model,\n ...(tier !== undefined ? { tier } : {}),\n reason,\n evidence,\n }\n}\n\nfunction parseDefer(raw: Record<string, unknown>): DeferDecision | undefined {\n const reason = parseVocab(raw[\"reason\"], SCHEDULER_DEFER_REASONS)\n if (reason === undefined) return undefined\n const detail = parseDetailBounded(raw[\"detail\"])\n return { kind: \"defer\", reason, ...(detail !== undefined ? { detail } : {}) }\n}\n\nfunction parsePark(raw: Record<string, unknown>): ParkDecision | undefined {\n const reason = parseVocab(raw[\"reason\"], PARK_REASONS)\n if (reason === undefined) return undefined\n const detail = parseDetailBounded(raw[\"detail\"])\n return { kind: \"park\", reason, ...(detail !== undefined ? { detail } : {}) }\n}\n\nfunction parseEscalate(raw: Record<string, unknown>): EscalateDecision | undefined {\n const target = parseModelRef(raw[\"target\"])\n const step = parseVocab(raw[\"step\"], ESCALATION_STEPS)\n const tier = parseTier(raw[\"tier\"])\n const budgetRemainingRaw = raw[\"budgetRemaining\"]\n if (\n target === undefined ||\n step === undefined ||\n tier === undefined ||\n !isFiniteNonNegative(budgetRemainingRaw)\n ) {\n return undefined\n }\n const evidenceRaw = raw[\"evidence\"]\n if (!Array.isArray(evidenceRaw)) return undefined\n const evidence: EscalationEvidence[] = []\n for (const entry of evidenceRaw.slice(0, 4)) {\n if (!isRecord(entry)) continue\n const kind = parseVocab(entry[\"kind\"], ESCALATION_EVIDENCE_KINDS)\n if (kind === undefined) continue\n const detail = parseDetailBounded(entry[\"detail\"])\n evidence.push({ kind, ...(detail !== undefined ? { detail } : {}) })\n }\n return {\n kind: \"escalate\",\n target,\n step,\n tier,\n evidence,\n budgetRemaining: budgetRemainingRaw,\n }\n}\n\n/** Max `observe-only` nesting accepted on read (the scheduler produces ≤ 1). */\nconst MAX_PARSE_DEPTH = 2\n\nfunction parseObserveOnly(\n raw: Record<string, unknown>,\n depth: number,\n): ObserveOnlyDecision | undefined {\n if (depth >= MAX_PARSE_DEPTH) return undefined\n const would = parseDecision(raw[\"would\"], depth + 1)\n return would === undefined ? undefined : { kind: \"observe-only\", would }\n}\n\nfunction parseDecision(raw: unknown, depth = 0): Decision | undefined {\n if (!isRecord(raw)) return undefined\n switch (raw[\"kind\"]) {\n case \"apply\":\n return parseApply(raw)\n case \"defer\":\n return parseDefer(raw)\n case \"park\":\n return parsePark(raw)\n case \"escalate\":\n return parseEscalate(raw)\n case \"observe-only\":\n return parseObserveOnly(raw, depth)\n default:\n return undefined\n }\n}\n\nfunction parseOutcome(raw: unknown): ReceiptOutcome | undefined {\n if (!isRecord(raw)) return undefined\n switch (raw[\"status\"]) {\n case \"succeeded\":\n return { status: \"succeeded\" }\n case \"failed\": {\n const category = parseVocab(raw[\"category\"], FAILURE_CATEGORIES)\n return category === undefined ? undefined : { status: \"failed\", category }\n }\n case \"interrupted\": {\n const reason = parseVocab(raw[\"reason\"], INTERRUPT_REASONS)\n return reason === undefined ? undefined : { status: \"interrupted\", reason }\n }\n default:\n return undefined\n }\n}\n\nfunction parseReceipt(raw: unknown): Receipt | undefined {\n if (!isRecord(raw)) return undefined\n const id = raw[\"id\"]\n const at = raw[\"at\"]\n const sessionID = parseOptionalBounded(raw[\"sessionID\"])\n const decision = parseDecision(raw[\"decision\"])\n if (\n !(isFiniteNonNegative(id) && Number.isInteger(id) && isFiniteNonNegative(at)) ||\n sessionID === undefined ||\n decision === undefined\n ) {\n return undefined\n }\n const unitID = parseOptionalBounded(raw[\"unitID\"])\n const tier = parseTier(raw[\"tier\"])\n // A present-but-invalid outcome invalidates the whole record: stripping\n // just the outcome would break first-outcome-wins across a restart (the\n // receipt would accept a later, different outcome as its first).\n let outcome: ReceiptOutcome | undefined\n if (raw[\"outcome\"] !== undefined) {\n outcome = parseOutcome(raw[\"outcome\"])\n if (outcome === undefined) return undefined\n }\n return {\n id,\n at,\n sessionID,\n ...(unitID !== undefined ? { unitID } : {}),\n ...(tier !== undefined ? { tier } : {}),\n decision,\n ...(outcome !== undefined ? { outcome } : {}),\n }\n}\n\n/**\n * Parse a persisted receipts payload. Accepts the versioned shape and a\n * versionless draft array; anything else yields no receipts.\n */\nexport function parseReceiptsPayload(raw: unknown): Receipt[] {\n let list: unknown\n if (isRecord(raw)) {\n const version = raw[\"version\"]\n if (version !== RECEIPTS_VERSION && version !== undefined) return []\n list = raw[\"receipts\"]\n } else if (Array.isArray(raw)) {\n list = raw\n }\n if (!Array.isArray(list)) return []\n const parsed = list\n .map(parseReceipt)\n .filter((receipt): receipt is Receipt => receipt !== undefined)\n return parsed.length > MAX_RECEIPTS ? parsed.slice(-MAX_RECEIPTS) : parsed\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import type { Receipt, ReceiptInput, ReceiptOutcome, ReceiptsStorage } from "./types.js";
2
+ export interface ReceiptRef {
3
+ readonly sessionID: string;
4
+ /** When given, only receipts of that unit are updated; otherwise only receipts without a unit. */
5
+ readonly unitID?: string | undefined;
6
+ }
7
+ export declare class ReceiptStore {
8
+ #private;
9
+ private readonly storage;
10
+ constructor(storage: ReceiptsStorage);
11
+ /** Rebuild from storage (restart behavior). A read failure yields an empty store. */
12
+ load(): Promise<void>;
13
+ /** All retained receipts, oldest → newest. */
14
+ snapshot(): readonly Receipt[];
15
+ record(input: ReceiptInput): Promise<Receipt>;
16
+ /**
17
+ * Attach a unit outcome to matching receipts that do not have one yet
18
+ * (first outcome wins). Returns how many receipts were updated; zero
19
+ * updates do not touch storage.
20
+ */
21
+ attachOutcome(ref: ReceiptRef, outcome: ReceiptOutcome): Promise<number>;
22
+ /**
23
+ * Stamps the unit that just opened onto the session's pending unit-less
24
+ * admission receipts (recorded in the awaited prompt hook, before the unit
25
+ * existed). After adoption the receipt carries the unitID, so outcomes
26
+ * attach to it like any unit-scoped receipt.
27
+ *
28
+ * One opened unit adopts ONE admission — the newest pending receipt. With
29
+ * several pending receipts (a prompt admitted but superseded before its
30
+ * unit ever opened), stamping all of them would attach this unit's outcome
31
+ * to foreign admission decisions; the older receipts stay pending instead.
32
+ */
33
+ adoptUnit(sessionID: string, unitID: string): Promise<number>;
34
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Bounded receipt store (ADP-030). Receipts live in a ring capped at
3
+ * `MAX_RECEIPTS` (newest kept, oldest evicted) and persist through the
4
+ * storage seam on every mutation. Ids are monotonic across restarts (they
5
+ * continue from the persisted maximum) so a receipt can always be told apart
6
+ * from its neighbors. Write failures propagate to the caller — the wiring
7
+ * layer's fail-open wrapper decides how to degrade (ADP-031) — and the
8
+ * in-memory ring is only advanced after a successful write, so memory and
9
+ * disk never diverge.
10
+ */
11
+ import { boundDecision, boundText } from "./bound.js";
12
+ import { parseReceiptsPayload } from "./parse.js";
13
+ import { MAX_RECEIPTS, RECEIPTS_VERSION } from "./types.js";
14
+ export class ReceiptStore {
15
+ storage;
16
+ #receipts = [];
17
+ #counter = 0;
18
+ #queue = Promise.resolve();
19
+ constructor(storage) {
20
+ this.storage = storage;
21
+ }
22
+ /** Rebuild from storage (restart behavior). A read failure yields an empty store. */
23
+ load() {
24
+ return this.#serialize(() => this.#load());
25
+ }
26
+ async #load() {
27
+ let raw;
28
+ try {
29
+ raw = await this.storage.read();
30
+ }
31
+ catch {
32
+ this.#receipts = [];
33
+ return;
34
+ }
35
+ const parsed = parseReceiptsPayload(raw);
36
+ this.#receipts = parsed;
37
+ this.#counter = parsed.reduce((max, receipt) => Math.max(max, receipt.id), 0);
38
+ }
39
+ /** All retained receipts, oldest → newest. */
40
+ snapshot() {
41
+ return this.#receipts;
42
+ }
43
+ record(input) {
44
+ return this.#serialize(() => this.#record(input));
45
+ }
46
+ async #record(input) {
47
+ // A receipt with no session can never be attributed to anything, and the
48
+ // read path drops empty identifiers — persisting one would make it vanish
49
+ // across a restart. Throw instead; the wiring layer fail-open-catches.
50
+ if (input.sessionID.trim() === "") {
51
+ throw new Error("invalid receipt: sessionID must be a non-empty string");
52
+ }
53
+ const id = this.#counter + 1;
54
+ const receipt = {
55
+ id,
56
+ at: input.at,
57
+ // Identifiers are bounded with the same bound the read path applies, so
58
+ // what is persisted is exactly what comes back (types.ts invariant).
59
+ sessionID: boundText(input.sessionID),
60
+ ...(input.unitID !== undefined ? { unitID: boundText(input.unitID) } : {}),
61
+ ...(input.tier !== undefined ? { tier: input.tier } : {}),
62
+ decision: boundDecision(input.decision),
63
+ };
64
+ const next = this.#receipts.length >= MAX_RECEIPTS
65
+ ? [...this.#receipts.slice(1 - MAX_RECEIPTS), receipt]
66
+ : [...this.#receipts, receipt];
67
+ // Persist BEFORE committing memory: a write failure must leave memory and
68
+ // disk consistent (the caller decides how to fail open).
69
+ await this.#persist(next);
70
+ this.#receipts = next;
71
+ this.#counter = id;
72
+ return receipt;
73
+ }
74
+ /**
75
+ * Attach a unit outcome to matching receipts that do not have one yet
76
+ * (first outcome wins). Returns how many receipts were updated; zero
77
+ * updates do not touch storage.
78
+ */
79
+ attachOutcome(ref, outcome) {
80
+ return this.#serialize(() => this.#attachOutcome(ref, outcome));
81
+ }
82
+ async #attachOutcome(ref, outcome) {
83
+ let updated = 0;
84
+ const next = this.#receipts.map((receipt) => {
85
+ if (receipt.outcome !== undefined)
86
+ return receipt;
87
+ if (receipt.sessionID !== ref.sessionID)
88
+ return receipt;
89
+ const matches = ref.unitID !== undefined ? receipt.unitID === ref.unitID : receipt.unitID === undefined;
90
+ if (!matches)
91
+ return receipt;
92
+ updated++;
93
+ return { ...receipt, outcome };
94
+ });
95
+ if (updated === 0)
96
+ return 0;
97
+ await this.#persist(next);
98
+ this.#receipts = next;
99
+ return updated;
100
+ }
101
+ /**
102
+ * Stamps the unit that just opened onto the session's pending unit-less
103
+ * admission receipts (recorded in the awaited prompt hook, before the unit
104
+ * existed). After adoption the receipt carries the unitID, so outcomes
105
+ * attach to it like any unit-scoped receipt.
106
+ *
107
+ * One opened unit adopts ONE admission — the newest pending receipt. With
108
+ * several pending receipts (a prompt admitted but superseded before its
109
+ * unit ever opened), stamping all of them would attach this unit's outcome
110
+ * to foreign admission decisions; the older receipts stay pending instead.
111
+ */
112
+ adoptUnit(sessionID, unitID) {
113
+ return this.#serialize(() => this.#adoptUnit(sessionID, unitID));
114
+ }
115
+ async #adoptUnit(sessionID, unitID) {
116
+ let target;
117
+ this.#receipts.forEach((receipt, index) => {
118
+ if (receipt.unitID !== undefined || receipt.outcome !== undefined)
119
+ return;
120
+ if (receipt.sessionID !== sessionID)
121
+ return;
122
+ target = index;
123
+ });
124
+ if (target === undefined)
125
+ return 0;
126
+ const at = target;
127
+ const next = this.#receipts.map((receipt, index) => index === at ? { ...receipt, unitID } : receipt);
128
+ await this.#persist(next);
129
+ this.#receipts = next;
130
+ return 1;
131
+ }
132
+ // Include reads in the chain so a reload cannot overwrite a newer commit.
133
+ // A failed write rejects its caller without blocking subsequent operations.
134
+ #serialize(operation) {
135
+ const run = this.#queue.then(operation);
136
+ this.#queue = run.catch(() => undefined);
137
+ return run;
138
+ }
139
+ /** Writes the given next state; the caller commits memory only on success. */
140
+ async #persist(receipts) {
141
+ const payload = {
142
+ version: RECEIPTS_VERSION,
143
+ receipts,
144
+ };
145
+ await this.storage.write(payload);
146
+ }
147
+ }
148
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/state/receipts/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bounded receipt store (ADP-030). Receipts live in a ring capped at\n * `MAX_RECEIPTS` (newest kept, oldest evicted) and persist through the\n * storage seam on every mutation. Ids are monotonic across restarts (they\n * continue from the persisted maximum) so a receipt can always be told apart\n * from its neighbors. Write failures propagate to the caller — the wiring\n * layer's fail-open wrapper decides how to degrade (ADP-031) — and the\n * in-memory ring is only advanced after a successful write, so memory and\n * disk never diverge.\n */\nimport { boundDecision, boundText } from \"./bound.js\"\nimport { parseReceiptsPayload } from \"./parse.js\"\nimport { MAX_RECEIPTS, RECEIPTS_VERSION } from \"./types.js\"\nimport type {\n Receipt,\n ReceiptInput,\n ReceiptOutcome,\n ReceiptsPayload,\n ReceiptsStorage,\n} from \"./types.js\"\n\nexport interface ReceiptRef {\n readonly sessionID: string\n /** When given, only receipts of that unit are updated; otherwise only receipts without a unit. */\n readonly unitID?: string | undefined\n}\n\nexport class ReceiptStore {\n #receipts: Receipt[] = []\n #counter = 0\n #queue: Promise<unknown> = Promise.resolve()\n\n constructor(private readonly storage: ReceiptsStorage) {}\n\n /** Rebuild from storage (restart behavior). A read failure yields an empty store. */\n load(): Promise<void> {\n return this.#serialize(() => this.#load())\n }\n\n async #load(): Promise<void> {\n let raw: unknown\n try {\n raw = await this.storage.read()\n } catch {\n this.#receipts = []\n return\n }\n const parsed = parseReceiptsPayload(raw)\n this.#receipts = parsed\n this.#counter = parsed.reduce((max, receipt) => Math.max(max, receipt.id), 0)\n }\n\n /** All retained receipts, oldest → newest. */\n snapshot(): readonly Receipt[] {\n return this.#receipts\n }\n\n record(input: ReceiptInput): Promise<Receipt> {\n return this.#serialize(() => this.#record(input))\n }\n\n async #record(input: ReceiptInput): Promise<Receipt> {\n // A receipt with no session can never be attributed to anything, and the\n // read path drops empty identifiers — persisting one would make it vanish\n // across a restart. Throw instead; the wiring layer fail-open-catches.\n if (input.sessionID.trim() === \"\") {\n throw new Error(\"invalid receipt: sessionID must be a non-empty string\")\n }\n const id = this.#counter + 1\n const receipt: Receipt = {\n id,\n at: input.at,\n // Identifiers are bounded with the same bound the read path applies, so\n // what is persisted is exactly what comes back (types.ts invariant).\n sessionID: boundText(input.sessionID),\n ...(input.unitID !== undefined ? { unitID: boundText(input.unitID) } : {}),\n ...(input.tier !== undefined ? { tier: input.tier } : {}),\n decision: boundDecision(input.decision),\n }\n const next =\n this.#receipts.length >= MAX_RECEIPTS\n ? [...this.#receipts.slice(1 - MAX_RECEIPTS), receipt]\n : [...this.#receipts, receipt]\n // Persist BEFORE committing memory: a write failure must leave memory and\n // disk consistent (the caller decides how to fail open).\n await this.#persist(next)\n this.#receipts = next\n this.#counter = id\n return receipt\n }\n\n /**\n * Attach a unit outcome to matching receipts that do not have one yet\n * (first outcome wins). Returns how many receipts were updated; zero\n * updates do not touch storage.\n */\n attachOutcome(ref: ReceiptRef, outcome: ReceiptOutcome): Promise<number> {\n return this.#serialize(() => this.#attachOutcome(ref, outcome))\n }\n\n async #attachOutcome(ref: ReceiptRef, outcome: ReceiptOutcome): Promise<number> {\n let updated = 0\n const next = this.#receipts.map((receipt) => {\n if (receipt.outcome !== undefined) return receipt\n if (receipt.sessionID !== ref.sessionID) return receipt\n const matches =\n ref.unitID !== undefined ? receipt.unitID === ref.unitID : receipt.unitID === undefined\n if (!matches) return receipt\n updated++\n return { ...receipt, outcome }\n })\n if (updated === 0) return 0\n await this.#persist(next)\n this.#receipts = next\n return updated\n }\n\n /**\n * Stamps the unit that just opened onto the session's pending unit-less\n * admission receipts (recorded in the awaited prompt hook, before the unit\n * existed). After adoption the receipt carries the unitID, so outcomes\n * attach to it like any unit-scoped receipt.\n *\n * One opened unit adopts ONE admission — the newest pending receipt. With\n * several pending receipts (a prompt admitted but superseded before its\n * unit ever opened), stamping all of them would attach this unit's outcome\n * to foreign admission decisions; the older receipts stay pending instead.\n */\n adoptUnit(sessionID: string, unitID: string): Promise<number> {\n return this.#serialize(() => this.#adoptUnit(sessionID, unitID))\n }\n\n async #adoptUnit(sessionID: string, unitID: string): Promise<number> {\n let target: number | undefined\n this.#receipts.forEach((receipt, index) => {\n if (receipt.unitID !== undefined || receipt.outcome !== undefined) return\n if (receipt.sessionID !== sessionID) return\n target = index\n })\n if (target === undefined) return 0\n const at = target\n const next = this.#receipts.map((receipt, index) =>\n index === at ? { ...receipt, unitID } : receipt,\n )\n await this.#persist(next)\n this.#receipts = next\n return 1\n }\n\n // Include reads in the chain so a reload cannot overwrite a newer commit.\n // A failed write rejects its caller without blocking subsequent operations.\n #serialize<T>(operation: () => Promise<T>): Promise<T> {\n const run = this.#queue.then(operation)\n this.#queue = run.catch(() => undefined)\n return run\n }\n\n /** Writes the given next state; the caller commits memory only on success. */\n async #persist(receipts: readonly Receipt[]): Promise<void> {\n const payload: ReceiptsPayload = {\n version: RECEIPTS_VERSION,\n receipts,\n }\n await this.storage.write(payload)\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Decision receipts (ADP-030): small bounded local records explaining why
3
+ * Adaptive made each decision. Receipts exist for explainability, debugging,
4
+ * tests, and a future outcome-learning policy — never injected into prompts,
5
+ * never leaving the machine. Retention is bounded (oldest evicted).
6
+ */
7
+ import type { Decision } from "../../core/scheduler/types.js";
8
+ import type { TierName } from "../../core/tier-policy/types.js";
9
+ import type { InterruptReason } from "../../core/work-unit/interruption.js";
10
+ import type { FailureCategory } from "../../health/classifier/types.js";
11
+ export declare const RECEIPTS_KEY = "coool.adaptive/receipts";
12
+ export declare const RECEIPTS_VERSION = 1;
13
+ /** Product bound — not a user knob. The ring keeps the newest receipts. */
14
+ export declare const MAX_RECEIPTS = 200;
15
+ /**
16
+ * Every free-text string stored in a receipt is truncated to this bound
17
+ * before persisting (and re-truncated on read). Boundedness is the
18
+ * no-secrets invariant: prompts, error bodies, and credentials have no
19
+ * field to live in and no room to hide in.
20
+ */
21
+ export declare const MAX_RECEIPT_TEXT = 128;
22
+ export interface ReceiptMeta {
23
+ /** Decision timestamp (adapter clock, ms epoch). */
24
+ readonly at: number;
25
+ readonly sessionID: string;
26
+ readonly unitID?: string | undefined;
27
+ /** Required tier when routing named one (pins carry no tier). */
28
+ readonly tier?: TierName | undefined;
29
+ }
30
+ export interface ReceiptInput extends ReceiptMeta {
31
+ /** The decision exactly as the scheduler/mode wrapper produced it. */
32
+ readonly decision: Decision;
33
+ }
34
+ /**
35
+ * Unit outcome attached after termination. Structured vocabulary only —
36
+ * never the raw error text (ADP-030: no secrets, bounded records).
37
+ */
38
+ export type ReceiptOutcome = {
39
+ readonly status: "succeeded";
40
+ } | {
41
+ readonly status: "failed";
42
+ readonly category: FailureCategory;
43
+ } | {
44
+ readonly status: "interrupted";
45
+ readonly reason: InterruptReason;
46
+ };
47
+ export interface Receipt extends ReceiptMeta {
48
+ /** Monotonic id assigned by the store; continues across restarts. */
49
+ readonly id: number;
50
+ readonly decision: Decision;
51
+ readonly outcome?: ReceiptOutcome;
52
+ }
53
+ export interface ReceiptsPayload {
54
+ readonly version: typeof RECEIPTS_VERSION;
55
+ readonly receipts: readonly Receipt[];
56
+ }
57
+ /** Storage seam — the adapter binds `RECEIPTS_KEY` to `ctx.storage`. */
58
+ export interface ReceiptsStorage {
59
+ read(): Promise<unknown>;
60
+ write(payload: ReceiptsPayload): Promise<void>;
61
+ }