open-multi-agent-kit 1.0.0 → 1.2.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 (244) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +2 -2
  3. package/dist/cli/help.d.ts.map +1 -1
  4. package/dist/cli/help.js +1 -0
  5. package/dist/cli/help.js.map +1 -1
  6. package/dist/coordination/awareness.d.ts +32 -0
  7. package/dist/coordination/awareness.d.ts.map +1 -0
  8. package/dist/coordination/awareness.js +55 -0
  9. package/dist/coordination/awareness.js.map +1 -0
  10. package/dist/coordination/broker.d.ts +79 -0
  11. package/dist/coordination/broker.d.ts.map +1 -0
  12. package/dist/coordination/broker.js +195 -0
  13. package/dist/coordination/broker.js.map +1 -0
  14. package/dist/coordination/index.d.ts +16 -0
  15. package/dist/coordination/index.d.ts.map +1 -0
  16. package/dist/coordination/index.js +16 -0
  17. package/dist/coordination/index.js.map +1 -0
  18. package/dist/coordination/integration.d.ts +59 -0
  19. package/dist/coordination/integration.d.ts.map +1 -0
  20. package/dist/coordination/integration.js +126 -0
  21. package/dist/coordination/integration.js.map +1 -0
  22. package/dist/coordination/operation.d.ts +106 -0
  23. package/dist/coordination/operation.d.ts.map +1 -0
  24. package/dist/coordination/operation.js +179 -0
  25. package/dist/coordination/operation.js.map +1 -0
  26. package/dist/coordination/resource.d.ts +33 -0
  27. package/dist/coordination/resource.d.ts.map +1 -0
  28. package/dist/coordination/resource.js +89 -0
  29. package/dist/coordination/resource.js.map +1 -0
  30. package/dist/coordination/session.d.ts +74 -0
  31. package/dist/coordination/session.d.ts.map +1 -0
  32. package/dist/coordination/session.js +143 -0
  33. package/dist/coordination/session.js.map +1 -0
  34. package/dist/coordination/types.d.ts +68 -0
  35. package/dist/coordination/types.d.ts.map +1 -0
  36. package/dist/coordination/types.js +26 -0
  37. package/dist/coordination/types.js.map +1 -0
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +28 -7
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/compaction/control-state.d.ts +41 -0
  42. package/dist/core/compaction/control-state.d.ts.map +1 -0
  43. package/dist/core/compaction/control-state.js +85 -0
  44. package/dist/core/compaction/control-state.js.map +1 -0
  45. package/dist/core/compaction/fallback.d.ts +60 -0
  46. package/dist/core/compaction/fallback.d.ts.map +1 -0
  47. package/dist/core/compaction/fallback.js +117 -0
  48. package/dist/core/compaction/fallback.js.map +1 -0
  49. package/dist/core/compaction/index.d.ts +2 -0
  50. package/dist/core/compaction/index.d.ts.map +1 -1
  51. package/dist/core/compaction/index.js +2 -0
  52. package/dist/core/compaction/index.js.map +1 -1
  53. package/dist/core/context-budget-headroom-candidates.d.ts +16 -1
  54. package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
  55. package/dist/core/context-budget-headroom-candidates.js +31 -13
  56. package/dist/core/context-budget-headroom-candidates.js.map +1 -1
  57. package/dist/core/context-budget-headroom-types.d.ts +7 -1
  58. package/dist/core/context-budget-headroom-types.d.ts.map +1 -1
  59. package/dist/core/context-budget-headroom-types.js +0 -1
  60. package/dist/core/context-budget-headroom-types.js.map +1 -1
  61. package/dist/core/context-budget-headroom.d.ts +10 -0
  62. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  63. package/dist/core/context-budget-headroom.js +11 -2
  64. package/dist/core/context-budget-headroom.js.map +1 -1
  65. package/dist/core/context-budget-v2-global-pass.d.ts +21 -0
  66. package/dist/core/context-budget-v2-global-pass.d.ts.map +1 -0
  67. package/dist/core/context-budget-v2-global-pass.js +203 -0
  68. package/dist/core/context-budget-v2-global-pass.js.map +1 -0
  69. package/dist/core/context-budget-v2-planned-items.d.ts +12 -0
  70. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -1
  71. package/dist/core/context-budget-v2-planned-items.js +21 -2
  72. package/dist/core/context-budget-v2-planned-items.js.map +1 -1
  73. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  74. package/dist/core/context-budget-v2-planner.js +18 -19
  75. package/dist/core/context-budget-v2-planner.js.map +1 -1
  76. package/dist/core/context-budget-v2-scoring.d.ts +7 -1
  77. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  78. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  79. package/dist/core/context-budget-v2-selection.d.ts +3 -0
  80. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  81. package/dist/core/context-budget-v2-selection.js +6 -2
  82. package/dist/core/context-budget-v2-selection.js.map +1 -1
  83. package/dist/core/context-budget-v2-types.d.ts +6 -1
  84. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  85. package/dist/core/context-budget-v2-types.js +6 -1
  86. package/dist/core/context-budget-v2-types.js.map +1 -1
  87. package/dist/core/provider-default-models.d.ts +1 -0
  88. package/dist/core/provider-default-models.d.ts.map +1 -1
  89. package/dist/core/provider-default-models.js +1 -0
  90. package/dist/core/provider-default-models.js.map +1 -1
  91. package/dist/core/provider-display-names.d.ts.map +1 -1
  92. package/dist/core/provider-display-names.js +1 -0
  93. package/dist/core/provider-display-names.js.map +1 -1
  94. package/dist/core/reasoning-router-resolver.d.ts +7 -0
  95. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  96. package/dist/core/reasoning-router-resolver.js +21 -3
  97. package/dist/core/reasoning-router-resolver.js.map +1 -1
  98. package/dist/core/reasoning-router-v4.d.ts +8 -5
  99. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  100. package/dist/core/reasoning-router-v4.js +8 -5
  101. package/dist/core/reasoning-router-v4.js.map +1 -1
  102. package/dist/core/session-compaction-service.d.ts +15 -0
  103. package/dist/core/session-compaction-service.d.ts.map +1 -1
  104. package/dist/core/session-compaction-service.js +17 -4
  105. package/dist/core/session-compaction-service.js.map +1 -1
  106. package/dist/core/todo-runtime-state.d.ts +12 -0
  107. package/dist/core/todo-runtime-state.d.ts.map +1 -1
  108. package/dist/core/todo-runtime-state.js +23 -0
  109. package/dist/core/todo-runtime-state.js.map +1 -1
  110. package/dist/index.d.ts +2 -0
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -0
  113. package/dist/index.js.map +1 -1
  114. package/dist/metacognition/calibration-selective.d.ts +78 -0
  115. package/dist/metacognition/calibration-selective.d.ts.map +1 -0
  116. package/dist/metacognition/calibration-selective.js +161 -0
  117. package/dist/metacognition/calibration-selective.js.map +1 -0
  118. package/dist/metacognition/calibration.d.ts +62 -0
  119. package/dist/metacognition/calibration.d.ts.map +1 -0
  120. package/dist/metacognition/calibration.js +122 -0
  121. package/dist/metacognition/calibration.js.map +1 -0
  122. package/dist/metacognition/checkpoint.d.ts +60 -0
  123. package/dist/metacognition/checkpoint.d.ts.map +1 -0
  124. package/dist/metacognition/checkpoint.js +57 -0
  125. package/dist/metacognition/checkpoint.js.map +1 -0
  126. package/dist/metacognition/context7.d.ts +48 -0
  127. package/dist/metacognition/context7.d.ts.map +1 -0
  128. package/dist/metacognition/context7.js +154 -0
  129. package/dist/metacognition/context7.js.map +1 -0
  130. package/dist/metacognition/decision.d.ts +44 -0
  131. package/dist/metacognition/decision.d.ts.map +1 -0
  132. package/dist/metacognition/decision.js +111 -0
  133. package/dist/metacognition/decision.js.map +1 -0
  134. package/dist/metacognition/evaluation.d.ts +73 -0
  135. package/dist/metacognition/evaluation.d.ts.map +1 -0
  136. package/dist/metacognition/evaluation.js +97 -0
  137. package/dist/metacognition/evaluation.js.map +1 -0
  138. package/dist/metacognition/index.d.ts +31 -0
  139. package/dist/metacognition/index.d.ts.map +1 -0
  140. package/dist/metacognition/index.js +31 -0
  141. package/dist/metacognition/index.js.map +1 -0
  142. package/dist/metacognition/knowledge-action.d.ts +43 -0
  143. package/dist/metacognition/knowledge-action.d.ts.map +1 -0
  144. package/dist/metacognition/knowledge-action.js +60 -0
  145. package/dist/metacognition/knowledge-action.js.map +1 -0
  146. package/dist/metacognition/knowledge.d.ts +84 -0
  147. package/dist/metacognition/knowledge.d.ts.map +1 -0
  148. package/dist/metacognition/knowledge.js +165 -0
  149. package/dist/metacognition/knowledge.js.map +1 -0
  150. package/dist/metacognition/obligations.d.ts +82 -0
  151. package/dist/metacognition/obligations.d.ts.map +1 -0
  152. package/dist/metacognition/obligations.js +139 -0
  153. package/dist/metacognition/obligations.js.map +1 -0
  154. package/dist/metacognition/observation-validity.d.ts +83 -0
  155. package/dist/metacognition/observation-validity.d.ts.map +1 -0
  156. package/dist/metacognition/observation-validity.js +118 -0
  157. package/dist/metacognition/observation-validity.js.map +1 -0
  158. package/dist/metacognition/observe.d.ts +45 -0
  159. package/dist/metacognition/observe.d.ts.map +1 -0
  160. package/dist/metacognition/observe.js +59 -0
  161. package/dist/metacognition/observe.js.map +1 -0
  162. package/dist/metacognition/policy.d.ts +53 -0
  163. package/dist/metacognition/policy.d.ts.map +1 -0
  164. package/dist/metacognition/policy.js +155 -0
  165. package/dist/metacognition/policy.js.map +1 -0
  166. package/dist/metacognition/predictions.d.ts +78 -0
  167. package/dist/metacognition/predictions.d.ts.map +1 -0
  168. package/dist/metacognition/predictions.js +181 -0
  169. package/dist/metacognition/predictions.js.map +1 -0
  170. package/dist/metacognition/retrieval.d.ts +53 -0
  171. package/dist/metacognition/retrieval.d.ts.map +1 -0
  172. package/dist/metacognition/retrieval.js +170 -0
  173. package/dist/metacognition/retrieval.js.map +1 -0
  174. package/dist/metacognition/risk.d.ts +48 -0
  175. package/dist/metacognition/risk.d.ts.map +1 -0
  176. package/dist/metacognition/risk.js +165 -0
  177. package/dist/metacognition/risk.js.map +1 -0
  178. package/dist/metacognition/route-economics.d.ts +95 -0
  179. package/dist/metacognition/route-economics.d.ts.map +1 -0
  180. package/dist/metacognition/route-economics.js +110 -0
  181. package/dist/metacognition/route-economics.js.map +1 -0
  182. package/dist/metacognition/runtime-bridge.d.ts +54 -0
  183. package/dist/metacognition/runtime-bridge.d.ts.map +1 -0
  184. package/dist/metacognition/runtime-bridge.js +140 -0
  185. package/dist/metacognition/runtime-bridge.js.map +1 -0
  186. package/dist/metacognition/skills.d.ts +42 -0
  187. package/dist/metacognition/skills.d.ts.map +1 -0
  188. package/dist/metacognition/skills.js +220 -0
  189. package/dist/metacognition/skills.js.map +1 -0
  190. package/dist/metacognition/state.d.ts +110 -0
  191. package/dist/metacognition/state.d.ts.map +1 -0
  192. package/dist/metacognition/state.js +52 -0
  193. package/dist/metacognition/state.js.map +1 -0
  194. package/dist/metacognition/validation.d.ts +19 -0
  195. package/dist/metacognition/validation.d.ts.map +1 -0
  196. package/dist/metacognition/validation.js +58 -0
  197. package/dist/metacognition/validation.js.map +1 -0
  198. package/dist/metacognition/verification.d.ts +93 -0
  199. package/dist/metacognition/verification.d.ts.map +1 -0
  200. package/dist/metacognition/verification.js +132 -0
  201. package/dist/metacognition/verification.js.map +1 -0
  202. package/dist/metacognition/verifier.d.ts +49 -0
  203. package/dist/metacognition/verifier.d.ts.map +1 -0
  204. package/dist/metacognition/verifier.js +88 -0
  205. package/dist/metacognition/verifier.js.map +1 -0
  206. package/dist/observation/identity.d.ts +14 -0
  207. package/dist/observation/identity.d.ts.map +1 -0
  208. package/dist/observation/identity.js +29 -0
  209. package/dist/observation/identity.js.map +1 -0
  210. package/dist/observation/index.d.ts +13 -0
  211. package/dist/observation/index.d.ts.map +1 -0
  212. package/dist/observation/index.js +13 -0
  213. package/dist/observation/index.js.map +1 -0
  214. package/dist/observation/observe-mode.d.ts +46 -0
  215. package/dist/observation/observe-mode.d.ts.map +1 -0
  216. package/dist/observation/observe-mode.js +83 -0
  217. package/dist/observation/observe-mode.js.map +1 -0
  218. package/dist/observation/store.d.ts +53 -0
  219. package/dist/observation/store.d.ts.map +1 -0
  220. package/dist/observation/store.js +126 -0
  221. package/dist/observation/store.js.map +1 -0
  222. package/dist/observation/types.d.ts +72 -0
  223. package/dist/observation/types.d.ts.map +1 -0
  224. package/dist/observation/types.js +9 -0
  225. package/dist/observation/types.js.map +1 -0
  226. package/dist/observation/view.d.ts +39 -0
  227. package/dist/observation/view.d.ts.map +1 -0
  228. package/dist/observation/view.js +173 -0
  229. package/dist/observation/view.js.map +1 -0
  230. package/docs/metacognition.md +51 -0
  231. package/docs/model-catalog-refresh.md +59 -0
  232. package/docs/providers.md +53 -0
  233. package/docs/runtime-algorithms.md +107 -3
  234. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  235. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  236. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  237. package/examples/extensions/gondolin/package-lock.json +2 -2
  238. package/examples/extensions/gondolin/package.json +1 -1
  239. package/examples/extensions/sandbox/package-lock.json +2 -2
  240. package/examples/extensions/sandbox/package.json +1 -1
  241. package/examples/extensions/with-deps/package-lock.json +2 -2
  242. package/examples/extensions/with-deps/package.json +1 -1
  243. package/npm-shrinkwrap.json +18 -18
  244. package/package.json +6 -6
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Algorithm D — meta-verification: checking that a check detects the defect
3
+ * it claims to cover. Spec §7.
4
+ *
5
+ * Obligation-scoped negative controls are evaluated over VALID, non-equivalent
6
+ * mutants only. Environment failures, compile-broken mutants, and timeouts are
7
+ * never counted as semantic detections. An empty denominator is `unknown`,
8
+ * never a score. This is an empirical detection rate for this mutant set,
9
+ * not the probability the code is correct.
10
+ */
11
+ import { ensure, finite, integer, lexical, member, text, unique } from "./validation.js";
12
+ const MUTANT_BOUND = 1024;
13
+ export function evaluateVerifier(obligationId, mutants, checksValidOnHealthy) {
14
+ text(obligationId, "obligationId", 256);
15
+ ensure(mutants.length <= MUTANT_BOUND, "too many mutants");
16
+ unique(mutants.map((m) => m.mutantId), "mutant ids");
17
+ const scoped = mutants.filter((m) => m.obligationId === obligationId);
18
+ const excluded = {
19
+ equivalent: 0,
20
+ "invalid-compile": 0,
21
+ "environment-failure": 0,
22
+ timeout: 0,
23
+ "killed-unintended": 0,
24
+ };
25
+ let detected = 0;
26
+ const survivors = [];
27
+ const blindSpots = [];
28
+ for (const m of scoped) {
29
+ member(m.classification, [
30
+ "killed-intended",
31
+ "survived",
32
+ "equivalent",
33
+ "invalid-compile",
34
+ "environment-failure",
35
+ "timeout",
36
+ "killed-unintended",
37
+ ], "mutant classification");
38
+ if (m.classification === "killed-intended" && m.intendedViolation) {
39
+ detected += 1;
40
+ }
41
+ else if (m.classification === "survived") {
42
+ survivors.push(m.mutantId);
43
+ blindSpots.push(`undetected-violation:${m.mutantId}`);
44
+ }
45
+ else {
46
+ excluded[m.classification] += 1;
47
+ if (m.classification === "invalid-compile" || m.classification === "environment-failure") {
48
+ blindSpots.push(`uninformative-mutant:${m.mutantId}`);
49
+ }
50
+ }
51
+ }
52
+ if (!checksValidOnHealthy)
53
+ blindSpots.push("check-invalid-on-healthy-candidate");
54
+ const denominator = detected + survivors.length;
55
+ return {
56
+ obligationId,
57
+ detectionRate: denominator === 0 ? "unknown" : detected / denominator,
58
+ evaluated: denominator,
59
+ excluded,
60
+ survivingMutantIds: survivors.sort(lexical),
61
+ blindSpots: blindSpots.sort(lexical),
62
+ };
63
+ }
64
+ export function relationApplicable(rel) {
65
+ return rel.requiredProperty;
66
+ }
67
+ export function progressKey(k) {
68
+ return [k.goalScope, k.candidateFamily, k.errorClass, k.approach, k.checkMethod, k.sourceFamily].join("\u001f");
69
+ }
70
+ export function isStagnant(current, history, maxRepetitions) {
71
+ integer(maxRepetitions, "maxRepetitions", 64);
72
+ const key = progressKey(current);
73
+ let repeats = 0;
74
+ for (const entry of history) {
75
+ if (entry.key === key && !entry.newEvidence && entry.resolvedObligations === 0 && entry.newValidChecks === 0) {
76
+ repeats += 1;
77
+ }
78
+ }
79
+ return repeats >= maxRepetitions;
80
+ }
81
+ /** §10.2 net strategy-switch score with hysteresis margin. */
82
+ export function netSwitchValue(estimatedLossDelta, cost, switchCost) {
83
+ finite(estimatedLossDelta, "estimatedLossDelta");
84
+ finite(cost, "cost");
85
+ finite(switchCost, "switchCost");
86
+ return estimatedLossDelta - cost - switchCost;
87
+ }
88
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../src/metacognition/verifier.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Algorithm D — meta-verification: checking that a check detects the defect\n * it claims to cover. Spec §7.\n *\n * Obligation-scoped negative controls are evaluated over VALID, non-equivalent\n * mutants only. Environment failures, compile-broken mutants, and timeouts are\n * never counted as semantic detections. An empty denominator is `unknown`,\n * never a score. This is an empirical detection rate for this mutant set,\n * not the probability the code is correct.\n */\nimport { ensure, finite, integer, lexical, member, text, unique } from \"./validation.ts\";\n\nexport type MutantClass =\n\t| \"killed-intended\"\n\t| \"survived\"\n\t| \"equivalent\"\n\t| \"invalid-compile\"\n\t| \"environment-failure\"\n\t| \"timeout\"\n\t| \"killed-unintended\";\nexport interface MutantRecord {\n\treadonly mutantId: string;\n\treadonly obligationId: string;\n\t/** The host classifies the outcome; this module never guesses. */\n\treadonly classification: MutantClass;\n\t/** Whether the intended obligation violation was what the check caught. */\n\treadonly intendedViolation: boolean;\n}\nexport interface VerifierEvaluation {\n\treadonly obligationId: string;\n\t/** D_v over valid non-equivalent mutants; \"unknown\" when empty. */\n\treadonly detectionRate: number | \"unknown\";\n\treadonly evaluated: number;\n\treadonly excluded: Readonly<Record<Exclude<MutantClass, \"killed-intended\" | \"survived\">, number>>;\n\treadonly survivingMutantIds: readonly string[];\n\treadonly blindSpots: readonly string[];\n}\n\nconst MUTANT_BOUND = 1024;\n\nexport function evaluateVerifier(\n\tobligationId: string,\n\tmutants: readonly MutantRecord[],\n\tchecksValidOnHealthy: boolean,\n): VerifierEvaluation {\n\ttext(obligationId, \"obligationId\", 256);\n\tensure(mutants.length <= MUTANT_BOUND, \"too many mutants\");\n\tunique(\n\t\tmutants.map((m) => m.mutantId),\n\t\t\"mutant ids\",\n\t);\n\tconst scoped = mutants.filter((m) => m.obligationId === obligationId);\n\tconst excluded: Record<Exclude<MutantClass, \"killed-intended\" | \"survived\">, number> = {\n\t\tequivalent: 0,\n\t\t\"invalid-compile\": 0,\n\t\t\"environment-failure\": 0,\n\t\ttimeout: 0,\n\t\t\"killed-unintended\": 0,\n\t};\n\tlet detected = 0;\n\tconst survivors: string[] = [];\n\tconst blindSpots: string[] = [];\n\tfor (const m of scoped) {\n\t\tmember(\n\t\t\tm.classification,\n\t\t\t[\n\t\t\t\t\"killed-intended\",\n\t\t\t\t\"survived\",\n\t\t\t\t\"equivalent\",\n\t\t\t\t\"invalid-compile\",\n\t\t\t\t\"environment-failure\",\n\t\t\t\t\"timeout\",\n\t\t\t\t\"killed-unintended\",\n\t\t\t],\n\t\t\t\"mutant classification\",\n\t\t);\n\t\tif (m.classification === \"killed-intended\" && m.intendedViolation) {\n\t\t\tdetected += 1;\n\t\t} else if (m.classification === \"survived\") {\n\t\t\tsurvivors.push(m.mutantId);\n\t\t\tblindSpots.push(`undetected-violation:${m.mutantId}`);\n\t\t} else {\n\t\t\texcluded[m.classification as keyof typeof excluded] += 1;\n\t\t\tif (m.classification === \"invalid-compile\" || m.classification === \"environment-failure\") {\n\t\t\t\tblindSpots.push(`uninformative-mutant:${m.mutantId}`);\n\t\t\t}\n\t\t}\n\t}\n\tif (!checksValidOnHealthy) blindSpots.push(\"check-invalid-on-healthy-candidate\");\n\tconst denominator = detected + survivors.length;\n\treturn {\n\t\tobligationId,\n\t\tdetectionRate: denominator === 0 ? \"unknown\" : detected / denominator,\n\t\tevaluated: denominator,\n\t\texcluded,\n\t\tsurvivingMutantIds: survivors.sort(lexical),\n\t\tblindSpots: blindSpots.sort(lexical),\n\t};\n}\n\n/**\n * §7.4 metamorphic relation: Pre(x,T) ⟹ R(f(x), f(T(x))). The relation must\n * be a declared required property of this program — never a universal rule.\n */\nexport interface MetamorphicRelation {\n\treadonly relationId: string;\n\treadonly description: string;\n\treadonly precondition: string;\n\treadonly requiredProperty: boolean;\n}\nexport function relationApplicable(rel: MetamorphicRelation): boolean {\n\treturn rel.requiredProperty;\n}\n\n/** §10.1 progress key: same key + no new adopted evidence = repetition. */\nexport interface ProgressKey {\n\treadonly goalScope: string;\n\treadonly candidateFamily: string;\n\treadonly errorClass: string;\n\treadonly approach: string;\n\treadonly checkMethod: string;\n\treadonly sourceFamily: string;\n}\nexport function progressKey(k: ProgressKey): string {\n\treturn [k.goalScope, k.candidateFamily, k.errorClass, k.approach, k.checkMethod, k.sourceFamily].join(\"\\u001f\");\n}\nexport function isStagnant(\n\tcurrent: ProgressKey,\n\thistory: readonly { key: string; newEvidence: boolean; resolvedObligations: number; newValidChecks: number }[],\n\tmaxRepetitions: number,\n): boolean {\n\tinteger(maxRepetitions, \"maxRepetitions\", 64);\n\tconst key = progressKey(current);\n\tlet repeats = 0;\n\tfor (const entry of history) {\n\t\tif (entry.key === key && !entry.newEvidence && entry.resolvedObligations === 0 && entry.newValidChecks === 0) {\n\t\t\trepeats += 1;\n\t\t}\n\t}\n\treturn repeats >= maxRepetitions;\n}\n\n/** §10.2 net strategy-switch score with hysteresis margin. */\nexport function netSwitchValue(estimatedLossDelta: number, cost: number, switchCost: number): number {\n\tfinite(estimatedLossDelta, \"estimatedLossDelta\");\n\tfinite(cost, \"cost\");\n\tfinite(switchCost, \"switchCost\");\n\treturn estimatedLossDelta - cost - switchCost;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ export declare function sha256Hex(data: Uint8Array | string): string;
2
+ export declare function observationId(input: {
3
+ readonly runId: string;
4
+ readonly operationId: string;
5
+ readonly sequence: number;
6
+ readonly rawDigest: string;
7
+ }): string;
8
+ /** Deterministic digest of a derived view's transform + source + params. */
9
+ export declare function viewDigestOf(input: {
10
+ readonly observationId: string;
11
+ readonly viewKind: string;
12
+ readonly params: string;
13
+ }): string;
14
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/observation/identity.ts"],"names":[],"mappings":"AAWA,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAI3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,CAMT;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,CAIT","sourcesContent":["/**\n * Observation identity and digests — U1.\n *\n * `observationId` binds an execution event (run, operation, sequence) to its\n * content digest. It is intentionally not a bare content hash: identical bytes\n * from two different executions are different events and must not collapse into\n * one record. Digests here are integrity checks, not provenance authentication.\n */\nimport { createHash } from \"node:crypto\";\nimport { integer, text } from \"../metacognition/validation.ts\";\n\nexport function sha256Hex(data: Uint8Array | string): string {\n\treturn createHash(\"sha256\")\n\t\t.update(data as Uint8Array)\n\t\t.digest(\"hex\");\n}\n\nexport function observationId(input: {\n\treadonly runId: string;\n\treadonly operationId: string;\n\treadonly sequence: number;\n\treadonly rawDigest: string;\n}): string {\n\ttext(input.runId, \"runId\", 256);\n\ttext(input.operationId, \"operationId\", 256);\n\tinteger(input.sequence, \"sequence\");\n\ttext(input.rawDigest, \"rawDigest\", 128);\n\treturn sha256Hex(`${input.runId}${input.operationId}${input.sequence}${input.rawDigest}`);\n}\n\n/** Deterministic digest of a derived view's transform + source + params. */\nexport function viewDigestOf(input: {\n\treadonly observationId: string;\n\treadonly viewKind: string;\n\treadonly params: string;\n}): string {\n\ttext(input.observationId, \"observationId\", 128);\n\ttext(input.viewKind, \"viewKind\", 32);\n\treturn sha256Hex(`${input.observationId}${input.viewKind}${input.params}`);\n}\n"]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Observation identity and digests — U1.
3
+ *
4
+ * `observationId` binds an execution event (run, operation, sequence) to its
5
+ * content digest. It is intentionally not a bare content hash: identical bytes
6
+ * from two different executions are different events and must not collapse into
7
+ * one record. Digests here are integrity checks, not provenance authentication.
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ import { integer, text } from "../metacognition/validation.js";
11
+ export function sha256Hex(data) {
12
+ return createHash("sha256")
13
+ .update(data)
14
+ .digest("hex");
15
+ }
16
+ export function observationId(input) {
17
+ text(input.runId, "runId", 256);
18
+ text(input.operationId, "operationId", 256);
19
+ integer(input.sequence, "sequence");
20
+ text(input.rawDigest, "rawDigest", 128);
21
+ return sha256Hex(`${input.runId}${input.operationId}${input.sequence}${input.rawDigest}`);
22
+ }
23
+ /** Deterministic digest of a derived view's transform + source + params. */
24
+ export function viewDigestOf(input) {
25
+ text(input.observationId, "observationId", 128);
26
+ text(input.viewKind, "viewKind", 32);
27
+ return sha256Hex(`${input.observationId}${input.viewKind}${input.params}`);
28
+ }
29
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/observation/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAE/D,MAAM,UAAU,SAAS,CAAC,IAAyB,EAAU;IAC5D,OAAO,UAAU,CAAC,QAAQ,CAAC;SACzB,MAAM,CAAC,IAAkB,CAAC;SAC1B,MAAM,CAAC,KAAK,CAAC,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,aAAa,CAAC,KAK7B,EAAU;IACV,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;AAAA,CAC1F;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,KAI5B,EAAU;IACV,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IACrC,OAAO,SAAS,CAAC,GAAG,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAAA,CAC3E","sourcesContent":["/**\n * Observation identity and digests — U1.\n *\n * `observationId` binds an execution event (run, operation, sequence) to its\n * content digest. It is intentionally not a bare content hash: identical bytes\n * from two different executions are different events and must not collapse into\n * one record. Digests here are integrity checks, not provenance authentication.\n */\nimport { createHash } from \"node:crypto\";\nimport { integer, text } from \"../metacognition/validation.ts\";\n\nexport function sha256Hex(data: Uint8Array | string): string {\n\treturn createHash(\"sha256\")\n\t\t.update(data as Uint8Array)\n\t\t.digest(\"hex\");\n}\n\nexport function observationId(input: {\n\treadonly runId: string;\n\treadonly operationId: string;\n\treadonly sequence: number;\n\treadonly rawDigest: string;\n}): string {\n\ttext(input.runId, \"runId\", 256);\n\ttext(input.operationId, \"operationId\", 256);\n\tinteger(input.sequence, \"sequence\");\n\ttext(input.rawDigest, \"rawDigest\", 128);\n\treturn sha256Hex(`${input.runId}${input.operationId}${input.sequence}${input.rawDigest}`);\n}\n\n/** Deterministic digest of a derived view's transform + source + params. */\nexport function viewDigestOf(input: {\n\treadonly observationId: string;\n\treadonly viewKind: string;\n\treadonly params: string;\n}): string {\n\ttext(input.observationId, \"observationId\", 128);\n\ttext(input.viewKind, \"viewKind\", 32);\n\treturn sha256Hex(`${input.observationId}${input.viewKind}${input.params}`);\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Observation kernel — U1/U2/U3 substrate of the upgraded SoL-Pi design.
3
+ *
4
+ * Pure, deterministic, bounded modules: raw-observation storage with byte-
5
+ * scoped reads, deterministic views with a coverage gate, and a shadow
6
+ * observation-mode recorder. No model calls, no I/O, no completion authority.
7
+ */
8
+ export * from "./identity.ts";
9
+ export * from "./observe-mode.ts";
10
+ export * from "./store.ts";
11
+ export * from "./types.ts";
12
+ export * from "./view.ts";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observation/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC","sourcesContent":["/**\n * Observation kernel — U1/U2/U3 substrate of the upgraded SoL-Pi design.\n *\n * Pure, deterministic, bounded modules: raw-observation storage with byte-\n * scoped reads, deterministic views with a coverage gate, and a shadow\n * observation-mode recorder. No model calls, no I/O, no completion authority.\n */\nexport * from \"./identity.ts\";\nexport * from \"./observe-mode.ts\";\nexport * from \"./store.ts\";\nexport * from \"./types.ts\";\nexport * from \"./view.ts\";\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Observation kernel — U1/U2/U3 substrate of the upgraded SoL-Pi design.
3
+ *
4
+ * Pure, deterministic, bounded modules: raw-observation storage with byte-
5
+ * scoped reads, deterministic views with a coverage gate, and a shadow
6
+ * observation-mode recorder. No model calls, no I/O, no completion authority.
7
+ */
8
+ export * from "./identity.js";
9
+ export * from "./observe-mode.js";
10
+ export * from "./store.js";
11
+ export * from "./types.js";
12
+ export * from "./view.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/observation/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC","sourcesContent":["/**\n * Observation kernel — U1/U2/U3 substrate of the upgraded SoL-Pi design.\n *\n * Pure, deterministic, bounded modules: raw-observation storage with byte-\n * scoped reads, deterministic views with a coverage gate, and a shadow\n * observation-mode recorder. No model calls, no I/O, no completion authority.\n */\nexport * from \"./identity.ts\";\nexport * from \"./observe-mode.ts\";\nexport * from \"./store.ts\";\nexport * from \"./types.ts\";\nexport * from \"./view.ts\";\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Observation-mode recorder — U1 shadow evaluation.
3
+ *
4
+ * Records what the coverage-gated deterministic view would have chosen and the
5
+ * upper-bound token saving, without changing what the model actually receives.
6
+ * A projected saving is an estimate, never an achieved saving — promotion out
7
+ * of observation mode requires a real comparison on the same budget.
8
+ */
9
+ import type { ObservationView } from "./types.ts";
10
+ export interface ObservationChoiceRecord {
11
+ readonly observationId: string;
12
+ readonly observedTokens: number;
13
+ readonly chosenTokens: number;
14
+ readonly chosenViewKind: string;
15
+ readonly savedTokensUpperBound: number;
16
+ readonly coverageStatus: string;
17
+ readonly infeasible: boolean;
18
+ }
19
+ export interface ObservationModeSummary {
20
+ readonly observations: number;
21
+ readonly observedTokens: number;
22
+ readonly chosenTokens: number;
23
+ readonly savedTokensUpperBound: number;
24
+ readonly infeasible: number;
25
+ readonly coverageComplete: number;
26
+ readonly coveragePartial: number;
27
+ readonly coverageUnknown: number;
28
+ }
29
+ export interface ObservationModeRecorder {
30
+ record(choice: {
31
+ readonly observationId: string;
32
+ readonly observedTokens: number;
33
+ readonly chosenView: ObservationView | null;
34
+ readonly requiredFactIds?: readonly string[];
35
+ }): ObservationChoiceRecord;
36
+ readonly records: readonly ObservationChoiceRecord[];
37
+ summary(): ObservationModeSummary;
38
+ }
39
+ /**
40
+ * Pure recorder: given what the model actually saw (`observedTokens`) and what
41
+ * the gated selector would have chosen, emit one decision record. A null
42
+ * `chosenView` means the candidate set was infeasible under the required
43
+ * coverage — recorded as such, never converted into an implicit success.
44
+ */
45
+ export declare function createObservationModeRecorder(): ObservationModeRecorder;
46
+ //# sourceMappingURL=observe-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observe-mode.d.ts","sourceRoot":"","sources":["../../src/observation/observe-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,MAAM,EAAE;QACd,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;QAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC7C,GAAG,uBAAuB,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACrD,OAAO,IAAI,sBAAsB,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,IAAI,uBAAuB,CA+DvE","sourcesContent":["/**\n * Observation-mode recorder — U1 shadow evaluation.\n *\n * Records what the coverage-gated deterministic view would have chosen and the\n * upper-bound token saving, without changing what the model actually receives.\n * A projected saving is an estimate, never an achieved saving — promotion out\n * of observation mode requires a real comparison on the same budget.\n */\n\nimport { ensure, integer, text } from \"../metacognition/validation.ts\";\nimport type { ObservationView } from \"./types.ts\";\n\nexport interface ObservationChoiceRecord {\n\treadonly observationId: string;\n\treadonly observedTokens: number;\n\treadonly chosenTokens: number;\n\treadonly chosenViewKind: string;\n\treadonly savedTokensUpperBound: number;\n\treadonly coverageStatus: string;\n\treadonly infeasible: boolean;\n}\n\nexport interface ObservationModeSummary {\n\treadonly observations: number;\n\treadonly observedTokens: number;\n\treadonly chosenTokens: number;\n\treadonly savedTokensUpperBound: number;\n\treadonly infeasible: number;\n\treadonly coverageComplete: number;\n\treadonly coveragePartial: number;\n\treadonly coverageUnknown: number;\n}\n\nexport interface ObservationModeRecorder {\n\trecord(choice: {\n\t\treadonly observationId: string;\n\t\treadonly observedTokens: number;\n\t\treadonly chosenView: ObservationView | null;\n\t\treadonly requiredFactIds?: readonly string[];\n\t}): ObservationChoiceRecord;\n\treadonly records: readonly ObservationChoiceRecord[];\n\tsummary(): ObservationModeSummary;\n}\n\n/**\n * Pure recorder: given what the model actually saw (`observedTokens`) and what\n * the gated selector would have chosen, emit one decision record. A null\n * `chosenView` means the candidate set was infeasible under the required\n * coverage — recorded as such, never converted into an implicit success.\n */\nexport function createObservationModeRecorder(): ObservationModeRecorder {\n\tconst records: ObservationChoiceRecord[] = [];\n\treturn {\n\t\trecord(choice) {\n\t\t\ttext(choice.observationId, \"observationId\", 128);\n\t\t\tinteger(choice.observedTokens, \"observedTokens\");\n\t\t\tconst chosen = choice.chosenView;\n\t\t\tconst record: ObservationChoiceRecord =\n\t\t\t\tchosen === null\n\t\t\t\t\t? Object.freeze({\n\t\t\t\t\t\t\tobservationId: choice.observationId,\n\t\t\t\t\t\t\tobservedTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenViewKind: \"infeasible\",\n\t\t\t\t\t\t\tsavedTokensUpperBound: 0,\n\t\t\t\t\t\t\tcoverageStatus: \"unknown\",\n\t\t\t\t\t\t\tinfeasible: true,\n\t\t\t\t\t\t})\n\t\t\t\t\t: Object.freeze({\n\t\t\t\t\t\t\tobservationId: choice.observationId,\n\t\t\t\t\t\t\tobservedTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenTokens: chosen.estimatedTokens,\n\t\t\t\t\t\t\tchosenViewKind: chosen.viewKind,\n\t\t\t\t\t\t\tsavedTokensUpperBound: Math.max(0, choice.observedTokens - chosen.estimatedTokens),\n\t\t\t\t\t\t\tcoverageStatus: chosen.coverageStatus,\n\t\t\t\t\t\t\tinfeasible: false,\n\t\t\t\t\t\t});\n\t\t\tensure(Number.isFinite(record.savedTokensUpperBound), \"savedTokensUpperBound must be finite\");\n\t\t\trecords.push(record);\n\t\t\treturn record;\n\t\t},\n\t\tget records() {\n\t\t\treturn records;\n\t\t},\n\t\tsummary() {\n\t\t\tlet observedTokens = 0;\n\t\t\tlet chosenTokens = 0;\n\t\t\tlet savedTokensUpperBound = 0;\n\t\t\tlet infeasible = 0;\n\t\t\tlet coverageComplete = 0;\n\t\t\tlet coveragePartial = 0;\n\t\t\tlet coverageUnknown = 0;\n\t\t\tfor (const r of records) {\n\t\t\t\tobservedTokens += r.observedTokens;\n\t\t\t\tchosenTokens += r.chosenTokens;\n\t\t\t\tsavedTokensUpperBound += r.savedTokensUpperBound;\n\t\t\t\tif (r.infeasible) infeasible += 1;\n\t\t\t\tif (r.coverageStatus === \"complete\") coverageComplete += 1;\n\t\t\t\telse if (r.coverageStatus === \"partial\") coveragePartial += 1;\n\t\t\t\telse coverageUnknown += 1;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tobservations: records.length,\n\t\t\t\tobservedTokens,\n\t\t\t\tchosenTokens,\n\t\t\t\tsavedTokensUpperBound,\n\t\t\t\tinfeasible,\n\t\t\t\tcoverageComplete,\n\t\t\t\tcoveragePartial,\n\t\t\t\tcoverageUnknown,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Observation-mode recorder — U1 shadow evaluation.
3
+ *
4
+ * Records what the coverage-gated deterministic view would have chosen and the
5
+ * upper-bound token saving, without changing what the model actually receives.
6
+ * A projected saving is an estimate, never an achieved saving — promotion out
7
+ * of observation mode requires a real comparison on the same budget.
8
+ */
9
+ import { ensure, integer, text } from "../metacognition/validation.js";
10
+ /**
11
+ * Pure recorder: given what the model actually saw (`observedTokens`) and what
12
+ * the gated selector would have chosen, emit one decision record. A null
13
+ * `chosenView` means the candidate set was infeasible under the required
14
+ * coverage — recorded as such, never converted into an implicit success.
15
+ */
16
+ export function createObservationModeRecorder() {
17
+ const records = [];
18
+ return {
19
+ record(choice) {
20
+ text(choice.observationId, "observationId", 128);
21
+ integer(choice.observedTokens, "observedTokens");
22
+ const chosen = choice.chosenView;
23
+ const record = chosen === null
24
+ ? Object.freeze({
25
+ observationId: choice.observationId,
26
+ observedTokens: choice.observedTokens,
27
+ chosenTokens: choice.observedTokens,
28
+ chosenViewKind: "infeasible",
29
+ savedTokensUpperBound: 0,
30
+ coverageStatus: "unknown",
31
+ infeasible: true,
32
+ })
33
+ : Object.freeze({
34
+ observationId: choice.observationId,
35
+ observedTokens: choice.observedTokens,
36
+ chosenTokens: chosen.estimatedTokens,
37
+ chosenViewKind: chosen.viewKind,
38
+ savedTokensUpperBound: Math.max(0, choice.observedTokens - chosen.estimatedTokens),
39
+ coverageStatus: chosen.coverageStatus,
40
+ infeasible: false,
41
+ });
42
+ ensure(Number.isFinite(record.savedTokensUpperBound), "savedTokensUpperBound must be finite");
43
+ records.push(record);
44
+ return record;
45
+ },
46
+ get records() {
47
+ return records;
48
+ },
49
+ summary() {
50
+ let observedTokens = 0;
51
+ let chosenTokens = 0;
52
+ let savedTokensUpperBound = 0;
53
+ let infeasible = 0;
54
+ let coverageComplete = 0;
55
+ let coveragePartial = 0;
56
+ let coverageUnknown = 0;
57
+ for (const r of records) {
58
+ observedTokens += r.observedTokens;
59
+ chosenTokens += r.chosenTokens;
60
+ savedTokensUpperBound += r.savedTokensUpperBound;
61
+ if (r.infeasible)
62
+ infeasible += 1;
63
+ if (r.coverageStatus === "complete")
64
+ coverageComplete += 1;
65
+ else if (r.coverageStatus === "partial")
66
+ coveragePartial += 1;
67
+ else
68
+ coverageUnknown += 1;
69
+ }
70
+ return {
71
+ observations: records.length,
72
+ observedTokens,
73
+ chosenTokens,
74
+ savedTokensUpperBound,
75
+ infeasible,
76
+ coverageComplete,
77
+ coveragePartial,
78
+ coverageUnknown,
79
+ };
80
+ },
81
+ };
82
+ }
83
+ //# sourceMappingURL=observe-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observe-mode.js","sourceRoot":"","sources":["../../src/observation/observe-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAmCvE;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,GAA4B;IACxE,MAAM,OAAO,GAA8B,EAAE,CAAC;IAC9C,OAAO;QACN,MAAM,CAAC,MAAM,EAAE;YACd,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;YACjD,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;YACjC,MAAM,MAAM,GACX,MAAM,KAAK,IAAI;gBACd,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;oBACd,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,YAAY,EAAE,MAAM,CAAC,cAAc;oBACnC,cAAc,EAAE,YAAY;oBAC5B,qBAAqB,EAAE,CAAC;oBACxB,cAAc,EAAE,SAAS;oBACzB,UAAU,EAAE,IAAI;iBAChB,CAAC;gBACH,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;oBACd,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,YAAY,EAAE,MAAM,CAAC,eAAe;oBACpC,cAAc,EAAE,MAAM,CAAC,QAAQ;oBAC/B,qBAAqB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,eAAe,CAAC;oBAClF,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,UAAU,EAAE,KAAK;iBACjB,CAAC,CAAC;YACN,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,qBAAqB,CAAC,EAAE,sCAAsC,CAAC,CAAC;YAC9F,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,OAAO,MAAM,CAAC;QAAA,CACd;QACD,IAAI,OAAO,GAAG;YACb,OAAO,OAAO,CAAC;QAAA,CACf;QACD,OAAO,GAAG;YACT,IAAI,cAAc,GAAG,CAAC,CAAC;YACvB,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,IAAI,qBAAqB,GAAG,CAAC,CAAC;YAC9B,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,IAAI,gBAAgB,GAAG,CAAC,CAAC;YACzB,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACzB,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC;gBACnC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC;gBAC/B,qBAAqB,IAAI,CAAC,CAAC,qBAAqB,CAAC;gBACjD,IAAI,CAAC,CAAC,UAAU;oBAAE,UAAU,IAAI,CAAC,CAAC;gBAClC,IAAI,CAAC,CAAC,cAAc,KAAK,UAAU;oBAAE,gBAAgB,IAAI,CAAC,CAAC;qBACtD,IAAI,CAAC,CAAC,cAAc,KAAK,SAAS;oBAAE,eAAe,IAAI,CAAC,CAAC;;oBACzD,eAAe,IAAI,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO;gBACN,YAAY,EAAE,OAAO,CAAC,MAAM;gBAC5B,cAAc;gBACd,YAAY;gBACZ,qBAAqB;gBACrB,UAAU;gBACV,gBAAgB;gBAChB,eAAe;gBACf,eAAe;aACf,CAAC;QAAA,CACF;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * Observation-mode recorder — U1 shadow evaluation.\n *\n * Records what the coverage-gated deterministic view would have chosen and the\n * upper-bound token saving, without changing what the model actually receives.\n * A projected saving is an estimate, never an achieved saving — promotion out\n * of observation mode requires a real comparison on the same budget.\n */\n\nimport { ensure, integer, text } from \"../metacognition/validation.ts\";\nimport type { ObservationView } from \"./types.ts\";\n\nexport interface ObservationChoiceRecord {\n\treadonly observationId: string;\n\treadonly observedTokens: number;\n\treadonly chosenTokens: number;\n\treadonly chosenViewKind: string;\n\treadonly savedTokensUpperBound: number;\n\treadonly coverageStatus: string;\n\treadonly infeasible: boolean;\n}\n\nexport interface ObservationModeSummary {\n\treadonly observations: number;\n\treadonly observedTokens: number;\n\treadonly chosenTokens: number;\n\treadonly savedTokensUpperBound: number;\n\treadonly infeasible: number;\n\treadonly coverageComplete: number;\n\treadonly coveragePartial: number;\n\treadonly coverageUnknown: number;\n}\n\nexport interface ObservationModeRecorder {\n\trecord(choice: {\n\t\treadonly observationId: string;\n\t\treadonly observedTokens: number;\n\t\treadonly chosenView: ObservationView | null;\n\t\treadonly requiredFactIds?: readonly string[];\n\t}): ObservationChoiceRecord;\n\treadonly records: readonly ObservationChoiceRecord[];\n\tsummary(): ObservationModeSummary;\n}\n\n/**\n * Pure recorder: given what the model actually saw (`observedTokens`) and what\n * the gated selector would have chosen, emit one decision record. A null\n * `chosenView` means the candidate set was infeasible under the required\n * coverage — recorded as such, never converted into an implicit success.\n */\nexport function createObservationModeRecorder(): ObservationModeRecorder {\n\tconst records: ObservationChoiceRecord[] = [];\n\treturn {\n\t\trecord(choice) {\n\t\t\ttext(choice.observationId, \"observationId\", 128);\n\t\t\tinteger(choice.observedTokens, \"observedTokens\");\n\t\t\tconst chosen = choice.chosenView;\n\t\t\tconst record: ObservationChoiceRecord =\n\t\t\t\tchosen === null\n\t\t\t\t\t? Object.freeze({\n\t\t\t\t\t\t\tobservationId: choice.observationId,\n\t\t\t\t\t\t\tobservedTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenViewKind: \"infeasible\",\n\t\t\t\t\t\t\tsavedTokensUpperBound: 0,\n\t\t\t\t\t\t\tcoverageStatus: \"unknown\",\n\t\t\t\t\t\t\tinfeasible: true,\n\t\t\t\t\t\t})\n\t\t\t\t\t: Object.freeze({\n\t\t\t\t\t\t\tobservationId: choice.observationId,\n\t\t\t\t\t\t\tobservedTokens: choice.observedTokens,\n\t\t\t\t\t\t\tchosenTokens: chosen.estimatedTokens,\n\t\t\t\t\t\t\tchosenViewKind: chosen.viewKind,\n\t\t\t\t\t\t\tsavedTokensUpperBound: Math.max(0, choice.observedTokens - chosen.estimatedTokens),\n\t\t\t\t\t\t\tcoverageStatus: chosen.coverageStatus,\n\t\t\t\t\t\t\tinfeasible: false,\n\t\t\t\t\t\t});\n\t\t\tensure(Number.isFinite(record.savedTokensUpperBound), \"savedTokensUpperBound must be finite\");\n\t\t\trecords.push(record);\n\t\t\treturn record;\n\t\t},\n\t\tget records() {\n\t\t\treturn records;\n\t\t},\n\t\tsummary() {\n\t\t\tlet observedTokens = 0;\n\t\t\tlet chosenTokens = 0;\n\t\t\tlet savedTokensUpperBound = 0;\n\t\t\tlet infeasible = 0;\n\t\t\tlet coverageComplete = 0;\n\t\t\tlet coveragePartial = 0;\n\t\t\tlet coverageUnknown = 0;\n\t\t\tfor (const r of records) {\n\t\t\t\tobservedTokens += r.observedTokens;\n\t\t\t\tchosenTokens += r.chosenTokens;\n\t\t\t\tsavedTokensUpperBound += r.savedTokensUpperBound;\n\t\t\t\tif (r.infeasible) infeasible += 1;\n\t\t\t\tif (r.coverageStatus === \"complete\") coverageComplete += 1;\n\t\t\t\telse if (r.coverageStatus === \"partial\") coveragePartial += 1;\n\t\t\t\telse coverageUnknown += 1;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tobservations: records.length,\n\t\t\t\tobservedTokens,\n\t\t\t\tchosenTokens,\n\t\t\t\tsavedTokensUpperBound,\n\t\t\t\tinfeasible,\n\t\t\t\tcoverageComplete,\n\t\t\t\tcoveragePartial,\n\t\t\t\tcoverageUnknown,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Bounded raw-observation store — U1.
3
+ *
4
+ * Session-scoped, in-memory, deterministic. Stores exact bytes with their
5
+ * execution identity, and serves byte-range reads that snap to UTF-8 code-point
6
+ * boundaries rather than emitting silently-corrupted text. Over-capacity puts
7
+ * fail closed with `archive-unavailable` instead of silently evicting evidence.
8
+ */
9
+ import type { ObservationKind, ObservationPrivacy, ObservationReadResult, ObservationStatus, RawObservation } from "./types.ts";
10
+ export interface ObservationStoreOptions {
11
+ readonly maxObservations?: number;
12
+ readonly maxTotalBytes?: number;
13
+ readonly maxObservationBytes?: number;
14
+ }
15
+ export declare class ObservationStore {
16
+ private readonly maxObservations;
17
+ private readonly maxTotalBytes;
18
+ private readonly maxObservationBytes;
19
+ private readonly byId;
20
+ private totalBytes;
21
+ constructor(options?: ObservationStoreOptions);
22
+ /** Store one raw observation. Throws (fails closed) on malformed input or capacity. */
23
+ put(input: {
24
+ readonly sessionId: string;
25
+ readonly runId: string;
26
+ readonly operationId: string;
27
+ readonly sequence: number;
28
+ readonly bytes: Uint8Array;
29
+ readonly status: ObservationStatus;
30
+ readonly kind?: ObservationKind;
31
+ readonly privacy?: ObservationPrivacy;
32
+ readonly sourceComplete?: boolean;
33
+ readonly toolCallId?: string;
34
+ }): RawObservation;
35
+ get(observationId: string): RawObservation | undefined;
36
+ has(observationId: string): boolean;
37
+ /**
38
+ * Scoped byte-range read. `scopeSessionId` must equal the observation's own
39
+ * session — a handle never crosses a session boundary. Byte offsets snap to
40
+ * UTF-8 boundaries; a `strict` range that lands mid-codepoint returns
41
+ * `utf8-boundary` instead of emitting corrupted text.
42
+ */
43
+ read(input: {
44
+ readonly observationId: string;
45
+ readonly scopeSessionId: string;
46
+ readonly byteOffset: number;
47
+ readonly maxBytes: number;
48
+ readonly strict?: boolean;
49
+ }): ObservationReadResult;
50
+ get size(): number;
51
+ get bytes(): number;
52
+ }
53
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/observation/store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACX,eAAe,EACf,kBAAkB,EAElB,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACtC;AAkBD,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAC1D,OAAO,CAAC,UAAU,CAAK;IAEvB,YAAY,OAAO,GAAE,uBAA4B,EAIhD;IAED,uFAAuF;IACvF,GAAG,CAAC,KAAK,EAAE;QACV,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;QAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;QACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;QAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC7B,GAAG,cAAc,CAmCjB;IAED,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAErD;IAED,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAElC;IAED;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE;QACX,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;KAC1B,GAAG,qBAAqB,CA+BxB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;CACD","sourcesContent":["/**\n * Bounded raw-observation store — U1.\n *\n * Session-scoped, in-memory, deterministic. Stores exact bytes with their\n * execution identity, and serves byte-range reads that snap to UTF-8 code-point\n * boundaries rather than emitting silently-corrupted text. Over-capacity puts\n * fail closed with `archive-unavailable` instead of silently evicting evidence.\n */\n\nimport { ensure, integer, text } from \"../metacognition/validation.ts\";\nimport { observationId as makeObservationId, sha256Hex } from \"./identity.ts\";\nimport type {\n\tObservationKind,\n\tObservationPrivacy,\n\tObservationRead,\n\tObservationReadResult,\n\tObservationStatus,\n\tRawObservation,\n} from \"./types.ts\";\n\nexport interface ObservationStoreOptions {\n\treadonly maxObservations?: number;\n\treadonly maxTotalBytes?: number;\n\treadonly maxObservationBytes?: number;\n}\n\nconst DEFAULT_MAX_OBSERVATIONS = 512;\nconst DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;\nconst DEFAULT_MAX_OBSERVATION_BYTES = 8 * 1024 * 1024;\n\nconst UTF8_DECODER = new TextDecoder(\"utf-8\", { fatal: false });\n\n/** Snap a byte offset down to the nearest UTF-8 code-point boundary. */\nfunction snapUtf8Boundary(bytes: Uint8Array, offset: number): { offset: number; normalized: boolean } {\n\tif (offset <= 0 || offset >= bytes.length)\n\t\treturn { offset: Math.max(0, Math.min(offset, bytes.length)), normalized: false };\n\t// A continuation byte is 0b10xxxxxx; walk back to a lead byte or ASCII.\n\tlet i = offset;\n\twhile (i > 0 && (bytes[i]! & 0xc0) === 0x80) i -= 1;\n\treturn { offset: i, normalized: i !== offset };\n}\n\nexport class ObservationStore {\n\tprivate readonly maxObservations: number;\n\tprivate readonly maxTotalBytes: number;\n\tprivate readonly maxObservationBytes: number;\n\tprivate readonly byId = new Map<string, RawObservation>();\n\tprivate totalBytes = 0;\n\n\tconstructor(options: ObservationStoreOptions = {}) {\n\t\tthis.maxObservations = options.maxObservations ?? DEFAULT_MAX_OBSERVATIONS;\n\t\tthis.maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;\n\t\tthis.maxObservationBytes = options.maxObservationBytes ?? DEFAULT_MAX_OBSERVATION_BYTES;\n\t}\n\n\t/** Store one raw observation. Throws (fails closed) on malformed input or capacity. */\n\tput(input: {\n\t\treadonly sessionId: string;\n\t\treadonly runId: string;\n\t\treadonly operationId: string;\n\t\treadonly sequence: number;\n\t\treadonly bytes: Uint8Array;\n\t\treadonly status: ObservationStatus;\n\t\treadonly kind?: ObservationKind;\n\t\treadonly privacy?: ObservationPrivacy;\n\t\treadonly sourceComplete?: boolean;\n\t\treadonly toolCallId?: string;\n\t}): RawObservation {\n\t\ttext(input.sessionId, \"sessionId\", 256);\n\t\ttext(input.runId, \"runId\", 256);\n\t\ttext(input.operationId, \"operationId\", 256);\n\t\tinteger(input.sequence, \"sequence\");\n\t\tensure(input.bytes instanceof Uint8Array, \"bytes must be a Uint8Array\");\n\t\tensure(input.bytes.length <= this.maxObservationBytes, \"observation exceeds per-item cap\");\n\t\tif (this.byId.size >= this.maxObservations || this.totalBytes + input.bytes.length > this.maxTotalBytes) {\n\t\t\tthrow new Error(\"archive-unavailable: observation store capacity exceeded\");\n\t\t}\n\t\tconst rawDigest = sha256Hex(input.bytes);\n\t\tconst id = makeObservationId({\n\t\t\trunId: input.runId,\n\t\t\toperationId: input.operationId,\n\t\t\tsequence: input.sequence,\n\t\t\trawDigest,\n\t\t});\n\t\tconst observation: RawObservation = Object.freeze({\n\t\t\tobservationId: id,\n\t\t\tsessionId: input.sessionId,\n\t\t\trunId: input.runId,\n\t\t\toperationId: input.operationId,\n\t\t\tsequence: input.sequence,\n\t\t\trawDigest,\n\t\t\tbyteLength: input.bytes.length,\n\t\t\tbytes: input.bytes,\n\t\t\tstatus: input.status,\n\t\t\tkind: input.kind ?? \"generic\",\n\t\t\tprivacy: input.privacy ?? \"raw-private\",\n\t\t\tsourceComplete: input.sourceComplete ?? true,\n\t\t\t...(input.toolCallId === undefined ? {} : { toolCallId: input.toolCallId }),\n\t\t});\n\t\tthis.byId.set(id, observation);\n\t\tthis.totalBytes += input.bytes.length;\n\t\treturn observation;\n\t}\n\n\tget(observationId: string): RawObservation | undefined {\n\t\treturn this.byId.get(observationId);\n\t}\n\n\thas(observationId: string): boolean {\n\t\treturn this.byId.has(observationId);\n\t}\n\n\t/**\n\t * Scoped byte-range read. `scopeSessionId` must equal the observation's own\n\t * session — a handle never crosses a session boundary. Byte offsets snap to\n\t * UTF-8 boundaries; a `strict` range that lands mid-codepoint returns\n\t * `utf8-boundary` instead of emitting corrupted text.\n\t */\n\tread(input: {\n\t\treadonly observationId: string;\n\t\treadonly scopeSessionId: string;\n\t\treadonly byteOffset: number;\n\t\treadonly maxBytes: number;\n\t\treadonly strict?: boolean;\n\t}): ObservationReadResult {\n\t\ttext(input.observationId, \"observationId\", 128);\n\t\ttext(input.scopeSessionId, \"scopeSessionId\", 256);\n\t\tinteger(input.byteOffset, \"byteOffset\");\n\t\tinteger(input.maxBytes, \"maxBytes\");\n\t\tensure(input.maxBytes > 0, \"maxBytes must be positive\");\n\t\tconst observation = this.byId.get(input.observationId);\n\t\tif (!observation) return { ok: false, error: \"observation-not-found\" };\n\t\tif (observation.sessionId !== input.scopeSessionId) {\n\t\t\treturn { ok: false, error: \"scope-mismatch\" };\n\t\t}\n\t\tif (input.byteOffset > observation.byteLength) {\n\t\t\treturn { ok: false, error: \"invalid-range\" };\n\t\t}\n\t\tconst snapped = snapUtf8Boundary(observation.bytes, input.byteOffset);\n\t\tif (input.strict && snapped.normalized) return { ok: false, error: \"utf8-boundary\" };\n\t\tconst start = snapped.offset;\n\t\tconst end = Math.min(observation.byteLength, start + input.maxBytes);\n\t\tconst chunk = observation.bytes.slice(start, end);\n\t\tconst read: ObservationRead = {\n\t\t\tobservationId: observation.observationId,\n\t\t\ttext: UTF8_DECODER.decode(chunk),\n\t\t\tbyteOffset: start,\n\t\t\tbyteLength: chunk.length,\n\t\t\tnextOffset: end,\n\t\t\teof: end >= observation.byteLength,\n\t\t\ttruncated: end < observation.byteLength,\n\t\t\tsourceComplete: observation.sourceComplete,\n\t\t\tnormalized: snapped.normalized,\n\t\t};\n\t\treturn { ok: true, read };\n\t}\n\n\tget size(): number {\n\t\treturn this.byId.size;\n\t}\n\n\tget bytes(): number {\n\t\treturn this.totalBytes;\n\t}\n}\n"]}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Bounded raw-observation store — U1.
3
+ *
4
+ * Session-scoped, in-memory, deterministic. Stores exact bytes with their
5
+ * execution identity, and serves byte-range reads that snap to UTF-8 code-point
6
+ * boundaries rather than emitting silently-corrupted text. Over-capacity puts
7
+ * fail closed with `archive-unavailable` instead of silently evicting evidence.
8
+ */
9
+ import { ensure, integer, text } from "../metacognition/validation.js";
10
+ import { observationId as makeObservationId, sha256Hex } from "./identity.js";
11
+ const DEFAULT_MAX_OBSERVATIONS = 512;
12
+ const DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
13
+ const DEFAULT_MAX_OBSERVATION_BYTES = 8 * 1024 * 1024;
14
+ const UTF8_DECODER = new TextDecoder("utf-8", { fatal: false });
15
+ /** Snap a byte offset down to the nearest UTF-8 code-point boundary. */
16
+ function snapUtf8Boundary(bytes, offset) {
17
+ if (offset <= 0 || offset >= bytes.length)
18
+ return { offset: Math.max(0, Math.min(offset, bytes.length)), normalized: false };
19
+ // A continuation byte is 0b10xxxxxx; walk back to a lead byte or ASCII.
20
+ let i = offset;
21
+ while (i > 0 && (bytes[i] & 0xc0) === 0x80)
22
+ i -= 1;
23
+ return { offset: i, normalized: i !== offset };
24
+ }
25
+ export class ObservationStore {
26
+ maxObservations;
27
+ maxTotalBytes;
28
+ maxObservationBytes;
29
+ byId = new Map();
30
+ totalBytes = 0;
31
+ constructor(options = {}) {
32
+ this.maxObservations = options.maxObservations ?? DEFAULT_MAX_OBSERVATIONS;
33
+ this.maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
34
+ this.maxObservationBytes = options.maxObservationBytes ?? DEFAULT_MAX_OBSERVATION_BYTES;
35
+ }
36
+ /** Store one raw observation. Throws (fails closed) on malformed input or capacity. */
37
+ put(input) {
38
+ text(input.sessionId, "sessionId", 256);
39
+ text(input.runId, "runId", 256);
40
+ text(input.operationId, "operationId", 256);
41
+ integer(input.sequence, "sequence");
42
+ ensure(input.bytes instanceof Uint8Array, "bytes must be a Uint8Array");
43
+ ensure(input.bytes.length <= this.maxObservationBytes, "observation exceeds per-item cap");
44
+ if (this.byId.size >= this.maxObservations || this.totalBytes + input.bytes.length > this.maxTotalBytes) {
45
+ throw new Error("archive-unavailable: observation store capacity exceeded");
46
+ }
47
+ const rawDigest = sha256Hex(input.bytes);
48
+ const id = makeObservationId({
49
+ runId: input.runId,
50
+ operationId: input.operationId,
51
+ sequence: input.sequence,
52
+ rawDigest,
53
+ });
54
+ const observation = Object.freeze({
55
+ observationId: id,
56
+ sessionId: input.sessionId,
57
+ runId: input.runId,
58
+ operationId: input.operationId,
59
+ sequence: input.sequence,
60
+ rawDigest,
61
+ byteLength: input.bytes.length,
62
+ bytes: input.bytes,
63
+ status: input.status,
64
+ kind: input.kind ?? "generic",
65
+ privacy: input.privacy ?? "raw-private",
66
+ sourceComplete: input.sourceComplete ?? true,
67
+ ...(input.toolCallId === undefined ? {} : { toolCallId: input.toolCallId }),
68
+ });
69
+ this.byId.set(id, observation);
70
+ this.totalBytes += input.bytes.length;
71
+ return observation;
72
+ }
73
+ get(observationId) {
74
+ return this.byId.get(observationId);
75
+ }
76
+ has(observationId) {
77
+ return this.byId.has(observationId);
78
+ }
79
+ /**
80
+ * Scoped byte-range read. `scopeSessionId` must equal the observation's own
81
+ * session — a handle never crosses a session boundary. Byte offsets snap to
82
+ * UTF-8 boundaries; a `strict` range that lands mid-codepoint returns
83
+ * `utf8-boundary` instead of emitting corrupted text.
84
+ */
85
+ read(input) {
86
+ text(input.observationId, "observationId", 128);
87
+ text(input.scopeSessionId, "scopeSessionId", 256);
88
+ integer(input.byteOffset, "byteOffset");
89
+ integer(input.maxBytes, "maxBytes");
90
+ ensure(input.maxBytes > 0, "maxBytes must be positive");
91
+ const observation = this.byId.get(input.observationId);
92
+ if (!observation)
93
+ return { ok: false, error: "observation-not-found" };
94
+ if (observation.sessionId !== input.scopeSessionId) {
95
+ return { ok: false, error: "scope-mismatch" };
96
+ }
97
+ if (input.byteOffset > observation.byteLength) {
98
+ return { ok: false, error: "invalid-range" };
99
+ }
100
+ const snapped = snapUtf8Boundary(observation.bytes, input.byteOffset);
101
+ if (input.strict && snapped.normalized)
102
+ return { ok: false, error: "utf8-boundary" };
103
+ const start = snapped.offset;
104
+ const end = Math.min(observation.byteLength, start + input.maxBytes);
105
+ const chunk = observation.bytes.slice(start, end);
106
+ const read = {
107
+ observationId: observation.observationId,
108
+ text: UTF8_DECODER.decode(chunk),
109
+ byteOffset: start,
110
+ byteLength: chunk.length,
111
+ nextOffset: end,
112
+ eof: end >= observation.byteLength,
113
+ truncated: end < observation.byteLength,
114
+ sourceComplete: observation.sourceComplete,
115
+ normalized: snapped.normalized,
116
+ };
117
+ return { ok: true, read };
118
+ }
119
+ get size() {
120
+ return this.byId.size;
121
+ }
122
+ get bytes() {
123
+ return this.totalBytes;
124
+ }
125
+ }
126
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/observation/store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAgB9E,MAAM,wBAAwB,GAAG,GAAG,CAAC;AACrC,MAAM,uBAAuB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACjD,MAAM,6BAA6B,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtD,MAAM,YAAY,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAEhE,wEAAwE;AACxE,SAAS,gBAAgB,CAAC,KAAiB,EAAE,MAAc,EAA2C;IACrG,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC,MAAM;QACxC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACnF,wEAAwE;IACxE,IAAI,CAAC,GAAG,MAAM,CAAC;IACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,KAAK,IAAI;QAAE,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;AAAA,CAC/C;AAED,MAAM,OAAO,gBAAgB;IACX,eAAe,CAAS;IACxB,aAAa,CAAS;IACtB,mBAAmB,CAAS;IAC5B,IAAI,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,OAAO,GAA4B,EAAE,EAAE;QAClD,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,wBAAwB,CAAC;QAC3E,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,uBAAuB,CAAC;QACtE,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,6BAA6B,CAAC;IAAA,CACxF;IAED,uFAAuF;IACvF,GAAG,CAAC,KAWH,EAAkB;QAClB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,KAAK,YAAY,UAAU,EAAE,4BAA4B,CAAC,CAAC;QACxE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,mBAAmB,EAAE,kCAAkC,CAAC,CAAC;QAC3F,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACzG,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;QAC7E,CAAC;QACD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,EAAE,GAAG,iBAAiB,CAAC;YAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS;SACT,CAAC,CAAC;QACH,MAAM,WAAW,GAAmB,MAAM,CAAC,MAAM,CAAC;YACjD,aAAa,EAAE,EAAE;YACjB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS;YACT,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;YAC9B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;YAC7B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,aAAa;YACvC,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;YAC5C,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC;SAC3E,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QACtC,OAAO,WAAW,CAAC;IAAA,CACnB;IAED,GAAG,CAAC,aAAqB,EAA8B;QACtD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAAA,CACpC;IAED,GAAG,CAAC,aAAqB,EAAW;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAAA,CACpC;IAED;;;;;OAKG;IACH,IAAI,CAAC,KAMJ,EAAyB;QACzB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACxC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,2BAA2B,CAAC,CAAC;QACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACvD,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;QACvE,IAAI,WAAW,CAAC,SAAS,KAAK,KAAK,CAAC,cAAc,EAAE,CAAC;YACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC/C,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC;YAC/C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;QAC9C,CAAC;QACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QACtE,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,UAAU;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;QACrF,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAClD,MAAM,IAAI,GAAoB;YAC7B,aAAa,EAAE,WAAW,CAAC,aAAa;YACxC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC;YAChC,UAAU,EAAE,KAAK;YACjB,UAAU,EAAE,KAAK,CAAC,MAAM;YACxB,UAAU,EAAE,GAAG;YACf,GAAG,EAAE,GAAG,IAAI,WAAW,CAAC,UAAU;YAClC,SAAS,EAAE,GAAG,GAAG,WAAW,CAAC,UAAU;YACvC,cAAc,EAAE,WAAW,CAAC,cAAc;YAC1C,UAAU,EAAE,OAAO,CAAC,UAAU;SAC9B,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAAA,CAC1B;IAED,IAAI,IAAI,GAAW;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAAA,CACtB;IAED,IAAI,KAAK,GAAW;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IAAA,CACvB;CACD","sourcesContent":["/**\n * Bounded raw-observation store — U1.\n *\n * Session-scoped, in-memory, deterministic. Stores exact bytes with their\n * execution identity, and serves byte-range reads that snap to UTF-8 code-point\n * boundaries rather than emitting silently-corrupted text. Over-capacity puts\n * fail closed with `archive-unavailable` instead of silently evicting evidence.\n */\n\nimport { ensure, integer, text } from \"../metacognition/validation.ts\";\nimport { observationId as makeObservationId, sha256Hex } from \"./identity.ts\";\nimport type {\n\tObservationKind,\n\tObservationPrivacy,\n\tObservationRead,\n\tObservationReadResult,\n\tObservationStatus,\n\tRawObservation,\n} from \"./types.ts\";\n\nexport interface ObservationStoreOptions {\n\treadonly maxObservations?: number;\n\treadonly maxTotalBytes?: number;\n\treadonly maxObservationBytes?: number;\n}\n\nconst DEFAULT_MAX_OBSERVATIONS = 512;\nconst DEFAULT_MAX_TOTAL_BYTES = 64 * 1024 * 1024;\nconst DEFAULT_MAX_OBSERVATION_BYTES = 8 * 1024 * 1024;\n\nconst UTF8_DECODER = new TextDecoder(\"utf-8\", { fatal: false });\n\n/** Snap a byte offset down to the nearest UTF-8 code-point boundary. */\nfunction snapUtf8Boundary(bytes: Uint8Array, offset: number): { offset: number; normalized: boolean } {\n\tif (offset <= 0 || offset >= bytes.length)\n\t\treturn { offset: Math.max(0, Math.min(offset, bytes.length)), normalized: false };\n\t// A continuation byte is 0b10xxxxxx; walk back to a lead byte or ASCII.\n\tlet i = offset;\n\twhile (i > 0 && (bytes[i]! & 0xc0) === 0x80) i -= 1;\n\treturn { offset: i, normalized: i !== offset };\n}\n\nexport class ObservationStore {\n\tprivate readonly maxObservations: number;\n\tprivate readonly maxTotalBytes: number;\n\tprivate readonly maxObservationBytes: number;\n\tprivate readonly byId = new Map<string, RawObservation>();\n\tprivate totalBytes = 0;\n\n\tconstructor(options: ObservationStoreOptions = {}) {\n\t\tthis.maxObservations = options.maxObservations ?? DEFAULT_MAX_OBSERVATIONS;\n\t\tthis.maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;\n\t\tthis.maxObservationBytes = options.maxObservationBytes ?? DEFAULT_MAX_OBSERVATION_BYTES;\n\t}\n\n\t/** Store one raw observation. Throws (fails closed) on malformed input or capacity. */\n\tput(input: {\n\t\treadonly sessionId: string;\n\t\treadonly runId: string;\n\t\treadonly operationId: string;\n\t\treadonly sequence: number;\n\t\treadonly bytes: Uint8Array;\n\t\treadonly status: ObservationStatus;\n\t\treadonly kind?: ObservationKind;\n\t\treadonly privacy?: ObservationPrivacy;\n\t\treadonly sourceComplete?: boolean;\n\t\treadonly toolCallId?: string;\n\t}): RawObservation {\n\t\ttext(input.sessionId, \"sessionId\", 256);\n\t\ttext(input.runId, \"runId\", 256);\n\t\ttext(input.operationId, \"operationId\", 256);\n\t\tinteger(input.sequence, \"sequence\");\n\t\tensure(input.bytes instanceof Uint8Array, \"bytes must be a Uint8Array\");\n\t\tensure(input.bytes.length <= this.maxObservationBytes, \"observation exceeds per-item cap\");\n\t\tif (this.byId.size >= this.maxObservations || this.totalBytes + input.bytes.length > this.maxTotalBytes) {\n\t\t\tthrow new Error(\"archive-unavailable: observation store capacity exceeded\");\n\t\t}\n\t\tconst rawDigest = sha256Hex(input.bytes);\n\t\tconst id = makeObservationId({\n\t\t\trunId: input.runId,\n\t\t\toperationId: input.operationId,\n\t\t\tsequence: input.sequence,\n\t\t\trawDigest,\n\t\t});\n\t\tconst observation: RawObservation = Object.freeze({\n\t\t\tobservationId: id,\n\t\t\tsessionId: input.sessionId,\n\t\t\trunId: input.runId,\n\t\t\toperationId: input.operationId,\n\t\t\tsequence: input.sequence,\n\t\t\trawDigest,\n\t\t\tbyteLength: input.bytes.length,\n\t\t\tbytes: input.bytes,\n\t\t\tstatus: input.status,\n\t\t\tkind: input.kind ?? \"generic\",\n\t\t\tprivacy: input.privacy ?? \"raw-private\",\n\t\t\tsourceComplete: input.sourceComplete ?? true,\n\t\t\t...(input.toolCallId === undefined ? {} : { toolCallId: input.toolCallId }),\n\t\t});\n\t\tthis.byId.set(id, observation);\n\t\tthis.totalBytes += input.bytes.length;\n\t\treturn observation;\n\t}\n\n\tget(observationId: string): RawObservation | undefined {\n\t\treturn this.byId.get(observationId);\n\t}\n\n\thas(observationId: string): boolean {\n\t\treturn this.byId.has(observationId);\n\t}\n\n\t/**\n\t * Scoped byte-range read. `scopeSessionId` must equal the observation's own\n\t * session — a handle never crosses a session boundary. Byte offsets snap to\n\t * UTF-8 boundaries; a `strict` range that lands mid-codepoint returns\n\t * `utf8-boundary` instead of emitting corrupted text.\n\t */\n\tread(input: {\n\t\treadonly observationId: string;\n\t\treadonly scopeSessionId: string;\n\t\treadonly byteOffset: number;\n\t\treadonly maxBytes: number;\n\t\treadonly strict?: boolean;\n\t}): ObservationReadResult {\n\t\ttext(input.observationId, \"observationId\", 128);\n\t\ttext(input.scopeSessionId, \"scopeSessionId\", 256);\n\t\tinteger(input.byteOffset, \"byteOffset\");\n\t\tinteger(input.maxBytes, \"maxBytes\");\n\t\tensure(input.maxBytes > 0, \"maxBytes must be positive\");\n\t\tconst observation = this.byId.get(input.observationId);\n\t\tif (!observation) return { ok: false, error: \"observation-not-found\" };\n\t\tif (observation.sessionId !== input.scopeSessionId) {\n\t\t\treturn { ok: false, error: \"scope-mismatch\" };\n\t\t}\n\t\tif (input.byteOffset > observation.byteLength) {\n\t\t\treturn { ok: false, error: \"invalid-range\" };\n\t\t}\n\t\tconst snapped = snapUtf8Boundary(observation.bytes, input.byteOffset);\n\t\tif (input.strict && snapped.normalized) return { ok: false, error: \"utf8-boundary\" };\n\t\tconst start = snapped.offset;\n\t\tconst end = Math.min(observation.byteLength, start + input.maxBytes);\n\t\tconst chunk = observation.bytes.slice(start, end);\n\t\tconst read: ObservationRead = {\n\t\t\tobservationId: observation.observationId,\n\t\t\ttext: UTF8_DECODER.decode(chunk),\n\t\t\tbyteOffset: start,\n\t\t\tbyteLength: chunk.length,\n\t\t\tnextOffset: end,\n\t\t\teof: end >= observation.byteLength,\n\t\t\ttruncated: end < observation.byteLength,\n\t\t\tsourceComplete: observation.sourceComplete,\n\t\t\tnormalized: snapped.normalized,\n\t\t};\n\t\treturn { ok: true, read };\n\t}\n\n\tget size(): number {\n\t\treturn this.byId.size;\n\t}\n\n\tget bytes(): number {\n\t\treturn this.totalBytes;\n\t}\n}\n"]}