@inflexa-ai/harness 0.16.0 → 0.18.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 (295) hide show
  1. package/dist/agents/conversation-agent.d.ts +3 -0
  2. package/dist/agents/conversation-agent.js +5 -2
  3. package/dist/agents/report-builder.d.ts +2 -0
  4. package/dist/agents/report-builder.js +2 -0
  5. package/dist/agents/sandbox/shared.d.ts +3 -0
  6. package/dist/agents/sandbox/shared.js +5 -0
  7. package/dist/agents/sandbox/types.d.ts +1 -1
  8. package/dist/app/chat-turn.d.ts +2 -0
  9. package/dist/app/chat-turn.js +11 -2
  10. package/dist/app/spawn-report-session.d.ts +85 -0
  11. package/dist/app/spawn-report-session.js +83 -0
  12. package/dist/app/synthesize-run.d.ts +3 -0
  13. package/dist/app/synthesize-run.js +2 -1
  14. package/dist/citations/__fixtures__/resolver.d.ts +3 -0
  15. package/dist/citations/__fixtures__/resolver.js +9 -0
  16. package/dist/citations/aggregate.d.ts +3 -0
  17. package/dist/citations/aggregate.js +44 -0
  18. package/dist/citations/clients/arxiv.d.ts +6 -0
  19. package/dist/citations/clients/arxiv.js +34 -0
  20. package/dist/citations/clients/common.d.ts +11 -0
  21. package/dist/citations/clients/common.js +43 -0
  22. package/dist/citations/clients/crossref.d.ts +8 -0
  23. package/dist/citations/clients/crossref.js +98 -0
  24. package/dist/citations/clients/doi-registry.d.ts +3 -0
  25. package/dist/citations/clients/doi-registry.js +95 -0
  26. package/dist/citations/clients/pubmed.d.ts +6 -0
  27. package/dist/citations/clients/pubmed.js +83 -0
  28. package/dist/citations/clients/semantic-scholar.d.ts +6 -0
  29. package/dist/citations/clients/semantic-scholar.js +60 -0
  30. package/dist/citations/compare.d.ts +9 -0
  31. package/dist/citations/compare.js +72 -0
  32. package/dist/citations/match.d.ts +11 -0
  33. package/dist/citations/match.js +173 -0
  34. package/dist/citations/normalize.d.ts +31 -0
  35. package/dist/citations/normalize.js +187 -0
  36. package/dist/citations/plan.d.ts +2 -0
  37. package/dist/citations/plan.js +59 -0
  38. package/dist/citations/resolve.d.ts +28 -0
  39. package/dist/citations/resolve.js +238 -0
  40. package/dist/citations/types.d.ts +583 -0
  41. package/dist/citations/types.js +129 -0
  42. package/dist/contracts/chat-events.d.ts +9 -0
  43. package/dist/contracts/index.d.ts +6 -2
  44. package/dist/contracts/index.js +3 -1
  45. package/dist/contracts/organ-system.d.ts +49 -0
  46. package/dist/contracts/organ-system.js +67 -0
  47. package/dist/contracts/report-blocks.d.ts +564 -0
  48. package/dist/contracts/report-blocks.js +117 -0
  49. package/dist/contracts/report-reference.d.ts +459 -0
  50. package/dist/contracts/report-reference.js +256 -0
  51. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  52. package/dist/contracts/schemas/chat-events.js +2 -0
  53. package/dist/contracts/schemas/chat-parts.d.ts +8 -8
  54. package/dist/contracts/severity.d.ts +15 -0
  55. package/dist/contracts/severity.js +10 -0
  56. package/dist/contracts/target-dossier.d.ts +8673 -12970
  57. package/dist/contracts/target-dossier.js +534 -591
  58. package/dist/data/safety-panel-schema.d.ts +18 -5
  59. package/dist/data/safety-panel-schema.js +4 -4
  60. package/dist/execution/artifact-metadata.js +1 -0
  61. package/dist/execution/report-runner.js +1 -3
  62. package/dist/execution/run-synthesis.d.ts +3 -0
  63. package/dist/execution/run-synthesis.js +3 -0
  64. package/dist/index.d.ts +12 -1
  65. package/dist/index.js +22 -0
  66. package/dist/lib/async-utils.d.ts +16 -4
  67. package/dist/lib/async-utils.js +20 -15
  68. package/dist/lib/cache.d.ts +8 -0
  69. package/dist/lib/cache.js +35 -0
  70. package/dist/lib/host-concurrency.js +1 -0
  71. package/dist/lib/rate-limit.d.ts +35 -0
  72. package/dist/lib/rate-limit.js +121 -0
  73. package/dist/literature/sources/arxiv.d.ts +22 -0
  74. package/dist/literature/sources/arxiv.js +103 -0
  75. package/dist/literature/sources/http.d.ts +28 -0
  76. package/dist/literature/sources/http.js +97 -0
  77. package/dist/literature/sources/pubmed.d.ts +68 -0
  78. package/dist/literature/sources/pubmed.js +266 -0
  79. package/dist/literature/sources/semantic-scholar.d.ts +24 -0
  80. package/dist/literature/sources/semantic-scholar.js +71 -0
  81. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  82. package/dist/loop/run-agent.js +58 -10
  83. package/dist/loop/tool-detail.d.ts +40 -0
  84. package/dist/loop/tool-detail.js +35 -3
  85. package/dist/loop/types.d.ts +13 -0
  86. package/dist/memory/thread-history.d.ts +10 -0
  87. package/dist/memory/thread-history.js +12 -1
  88. package/dist/memory/thread-store.d.ts +142 -25
  89. package/dist/memory/thread-store.js +228 -40
  90. package/dist/prompts/literature-reviewer.d.ts +1 -1
  91. package/dist/prompts/literature-reviewer.js +2 -0
  92. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  93. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  94. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  95. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  96. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  97. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  98. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  99. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  100. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  101. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  102. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  103. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  104. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  105. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  106. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  107. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  108. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  109. package/dist/providers/__fixtures__/session.d.ts +5 -4
  110. package/dist/providers/__fixtures__/session.js +4 -4
  111. package/dist/report-model/block-walk.d.ts +56 -0
  112. package/dist/report-model/block-walk.js +89 -0
  113. package/dist/report-model/draft-finish.d.ts +60 -0
  114. package/dist/report-model/draft-finish.js +56 -0
  115. package/dist/report-model/draft-operations.d.ts +125 -0
  116. package/dist/report-model/draft-operations.js +341 -0
  117. package/dist/report-model/draft-read.d.ts +59 -0
  118. package/dist/report-model/draft-read.js +109 -0
  119. package/dist/report-model/draft.d.ts +51 -0
  120. package/dist/report-model/draft.js +32 -0
  121. package/dist/report-model/fixture-resolver.d.ts +8 -0
  122. package/dist/report-model/fixture-resolver.js +252 -0
  123. package/dist/report-model/mint-snapshot.d.ts +30 -0
  124. package/dist/report-model/mint-snapshot.js +40 -0
  125. package/dist/report-model/reference-resolver.d.ts +113 -0
  126. package/dist/report-model/reference-resolver.js +60 -0
  127. package/dist/report-model/structural-validation.d.ts +22 -0
  128. package/dist/report-model/structural-validation.js +68 -0
  129. package/dist/report-model/validate.d.ts +40 -0
  130. package/dist/report-model/validate.js +80 -0
  131. package/dist/report-render/chart.d.ts +33 -0
  132. package/dist/report-render/chart.js +538 -0
  133. package/dist/report-render/page.d.ts +127 -0
  134. package/dist/report-render/page.js +307 -0
  135. package/dist/report-render/references.d.ts +32 -0
  136. package/dist/report-render/references.js +44 -0
  137. package/dist/report-render/render.d.ts +25 -0
  138. package/dist/report-render/render.js +125 -0
  139. package/dist/report-render/script-json.d.ts +13 -0
  140. package/dist/report-render/script-json.js +15 -0
  141. package/dist/report-render/types.d.ts +39 -0
  142. package/dist/report-render/types.js +10 -0
  143. package/dist/report-render/views/chart-view.d.ts +11 -0
  144. package/dist/report-render/views/chart-view.js +23 -0
  145. package/dist/report-render/views/page-view.d.ts +20 -0
  146. package/dist/report-render/views/page-view.js +49 -0
  147. package/dist/report-render/views/prose.d.ts +28 -0
  148. package/dist/report-render/views/prose.js +75 -0
  149. package/dist/report-render/views/references-view.d.ts +20 -0
  150. package/dist/report-render/views/references-view.js +64 -0
  151. package/dist/report-render/views/values.d.ts +42 -0
  152. package/dist/report-render/views/values.js +43 -0
  153. package/dist/runtime/assemble.d.ts +50 -4
  154. package/dist/runtime/assemble.js +47 -3
  155. package/dist/runtime/boot.d.ts +3 -3
  156. package/dist/runtime/boot.js +22 -5
  157. package/dist/runtime/dbos.d.ts +4 -4
  158. package/dist/state/artifacts.d.ts +16 -0
  159. package/dist/state/artifacts.js +18 -0
  160. package/dist/state/init.js +77 -1
  161. package/dist/state/purge-analysis.js +1 -0
  162. package/dist/state/report-versions.d.ts +132 -0
  163. package/dist/state/report-versions.js +194 -0
  164. package/dist/state/schema.d.ts +2 -2
  165. package/dist/tasks/data-profile.js +1 -0
  166. package/dist/tools/bio/chembl.js +1 -0
  167. package/dist/tools/bio/comptox.js +1 -0
  168. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  169. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  170. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -1
  171. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  172. package/dist/tools/bio/keys.d.ts +4 -2
  173. package/dist/tools/bio/keys.js +3 -2
  174. package/dist/tools/bio/lookup-annotation.js +1 -0
  175. package/dist/tools/bio/opentargets.d.ts +3 -3
  176. package/dist/tools/bio/opentargets.js +4 -3
  177. package/dist/tools/bio/pubchem.js +1 -0
  178. package/dist/tools/bio/pubmed.js +4 -4
  179. package/dist/tools/bio/search-clinical-trials.js +1 -0
  180. package/dist/tools/bio/search-faers.js +1 -0
  181. package/dist/tools/bio/search-geo-datasets.js +1 -0
  182. package/dist/tools/bio/search-interactions.js +1 -0
  183. package/dist/tools/bio/target-safety.d.ts +4 -6
  184. package/dist/tools/bio/target-safety.js +7 -6
  185. package/dist/tools/define-tool.d.ts +19 -12
  186. package/dist/tools/define-tool.js +4 -2
  187. package/dist/tools/display/show-user.js +1 -0
  188. package/dist/tools/iterate-report.js +2 -0
  189. package/dist/tools/lib/chembl-client.d.ts +2 -0
  190. package/dist/tools/lib/chembl-client.js +1 -0
  191. package/dist/tools/lib/monarch-client.d.ts +42 -0
  192. package/dist/tools/lib/monarch-client.js +100 -0
  193. package/dist/tools/lib/ncbi-utils.d.ts +3 -58
  194. package/dist/tools/lib/ncbi-utils.js +2 -286
  195. package/dist/tools/lib/pubmed-client.d.ts +5 -12
  196. package/dist/tools/lib/pubmed-client.js +20 -79
  197. package/dist/tools/report/build-report.js +1 -0
  198. package/dist/tools/report/mint-preview-url.js +1 -0
  199. package/dist/tools/report/preview-snapshot.js +1 -0
  200. package/dist/tools/report/submit-report.js +1 -0
  201. package/dist/tools/report/version-fs.js +4 -0
  202. package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
  203. package/dist/tools/report-authoring/authoring-tools.js +352 -0
  204. package/dist/tools/research/context7-docs.js +2 -0
  205. package/dist/tools/research/generate-analogy-report.js +3 -2
  206. package/dist/tools/research/generate-plan.js +3 -0
  207. package/dist/tools/research/index.d.ts +1 -0
  208. package/dist/tools/research/index.js +1 -0
  209. package/dist/tools/research/inspect-data-profile.js +4 -0
  210. package/dist/tools/research/inspect-run.d.ts +1 -1
  211. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  212. package/dist/tools/research/literature-reviewer.js +3 -0
  213. package/dist/tools/research/resolve-citation.d.ts +3 -0
  214. package/dist/tools/research/resolve-citation.js +15 -0
  215. package/dist/tools/research/search-arxiv.d.ts +3 -19
  216. package/dist/tools/research/search-arxiv.js +10 -105
  217. package/dist/tools/research/search-github-repos.js +1 -0
  218. package/dist/tools/research/search-semantic-scholar.d.ts +8 -33
  219. package/dist/tools/research/search-semantic-scholar.js +30 -88
  220. package/dist/tools/sandbox/list-available-packages.js +23 -0
  221. package/dist/tools/sandbox/list-available-refs.js +37 -2
  222. package/dist/tools/sandbox/report-blocker.js +1 -0
  223. package/dist/tools/sandbox/skills.js +2 -0
  224. package/dist/tools/workspace/file-stat.js +1 -0
  225. package/dist/tools/workspace/show-file.js +2 -0
  226. package/dist/tools/workspace/show-plan.js +1 -0
  227. package/dist/workflows/execute-analysis.d.ts +8 -1
  228. package/dist/workflows/execute-analysis.js +6 -5
  229. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  230. package/dist/workflows/execute-target-assessment.js +78 -16
  231. package/dist/workflows/sandbox-step.d.ts +5 -0
  232. package/dist/workflows/sandbox-step.js +1 -0
  233. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  234. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  235. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  236. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  237. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  238. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  239. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  240. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  241. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  242. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  243. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  244. package/dist/workflows/target-assessment/coverage.js +33 -29
  245. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  246. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  247. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  248. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  249. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  250. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  251. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  252. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  253. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  254. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  255. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  256. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  257. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  258. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  259. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  260. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  261. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  262. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  263. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  264. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  265. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  266. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  267. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  268. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  269. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  270. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  271. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  272. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  273. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  274. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  275. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  276. package/dist/workflows/target-assessment/metrics.js +1 -13
  277. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2149 -809
  278. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  279. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  280. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  281. package/dist/workflows/target-assessment/schemas.d.ts +48 -3
  282. package/dist/workflows/target-assessment/schemas.js +19 -0
  283. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  284. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  285. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +27 -63
  286. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  287. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +28 -155
  288. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  289. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  290. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  291. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  292. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  293. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  294. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  295. package/package.json +4 -1
@@ -14,14 +14,18 @@
14
14
  * `applicationVersion` stamp because all registration happens in this one call,
15
15
  * before launch — a blue/green drain treats them as a single cohort.
16
16
  */
17
+ import { type Result } from "neverthrow";
17
18
  import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
18
19
  import type { UsageRecorder } from "../billing/usage-recorder.js";
19
20
  import type { ResourcePolicy } from "../config/resource-limits.js";
20
21
  import type { AgentDefinition } from "../loop/types.js";
22
+ import type { ThreadType } from "../memory/thread-store.js";
21
23
  import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
22
24
  import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
23
25
  import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
24
26
  import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
27
+ import { type CitationResolverConfig } from "../citations/resolve.js";
28
+ import type { CitationResolver } from "../citations/types.js";
25
29
  /** Registered child sandbox-step callable the parent's child dispatch closes over. */
26
30
  export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
27
31
  /**
@@ -38,8 +42,8 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
38
42
  * conversation side.
39
43
  */
40
44
  export interface CoreWorkflowDeps {
41
- readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder">;
42
- readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder">;
45
+ readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver">;
46
+ readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver">;
43
47
  readonly executeTargetAssessment: Omit<ExecuteTargetAssessmentDeps, "usageRecorder">;
44
48
  readonly dataProfile: Omit<DataProfileDeps, "usageRecorder">;
45
49
  }
@@ -56,7 +60,7 @@ export interface RegisteredWorkflows {
56
60
  * cannot wire a stale callable, a policy that diverges from the one the
57
61
  * workflows see, or a recorder that only half the agent tree reports to.
58
62
  */
59
- export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder">;
63
+ export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver">;
60
64
  export interface CoreRuntimeDeps {
61
65
  readonly conversation: ConversationAssemblyDeps;
62
66
  readonly workflows: CoreWorkflowDeps;
@@ -72,9 +76,51 @@ export interface CoreRuntimeDeps {
72
76
  * `createNoopUsageRecorder()`.
73
77
  */
74
78
  readonly usageRecorder?: UsageRecorder;
79
+ /** Harness-owned citation capability configuration; no ambient lookup occurs. */
80
+ readonly citationResolverConfig?: CitationResolverConfig;
75
81
  }
82
+ /**
83
+ * The refusal a thread type carries no agent yet. `report` is a valid
84
+ * `ThreadType` with no registered agent until its builder plugs in at assembly,
85
+ * so resolution has to have a typed way to say "not that one" without throwing.
86
+ * The channel is permanent, not interim: `ThreadType` grows over the product's
87
+ * life, and a bare-`AgentDefinition` return would force every future member to
88
+ * register an agent in the same commit that adds the member.
89
+ */
90
+ export interface UnregisteredThreadType {
91
+ readonly type: "unregistered_thread_type";
92
+ readonly threadType: ThreadType;
93
+ }
94
+ /**
95
+ * How a thread's type selects the agent that runs its turns. Resolution is a
96
+ * synchronous record lookup, so the channel is plain `Result`, never
97
+ * `ResultAsync`: a registered type resolves to its assembled singleton, an
98
+ * unregistered one refuses on the error channel.
99
+ */
100
+ export interface ThreadAgentResolver {
101
+ forThread(type: ThreadType): Result<AgentDefinition, UnregisteredThreadType>;
102
+ }
103
+ /**
104
+ * Wrap a type→agent registry as the resolution surface. Held apart from
105
+ * `assembleCoreRuntime` so the resolution contract — a registered type resolves
106
+ * to the very object the registry holds, an unregistered one refuses — is
107
+ * exercisable without the DBOS registration `assembleCoreRuntime` performs.
108
+ *
109
+ * The registry holds assembled singletons, so `forThread` returns the same
110
+ * `AgentDefinition` object on every call for a given type: construction-time
111
+ * captures (an embedder's delegating provider handles, closure-wired tools)
112
+ * stay valid across every turn.
113
+ */
114
+ export declare function createThreadAgentResolver(registry: Partial<Record<ThreadType, AgentDefinition>>): ThreadAgentResolver;
76
115
  export interface CoreRuntime {
77
- readonly conversationAgent: AgentDefinition;
116
+ /**
117
+ * Thread→agent resolution — the only way to reach an assembled agent by
118
+ * thread type. Holds the singletons this call built; `conversation` resolves
119
+ * to the assembled conversation agent, every not-yet-registered type refuses
120
+ * on the `Result` channel.
121
+ */
122
+ readonly agents: ThreadAgentResolver;
78
123
  readonly workflows: RegisteredWorkflows;
124
+ readonly citationResolver: CitationResolver;
79
125
  }
80
126
  export declare function assembleCoreRuntime(deps: CoreRuntimeDeps): CoreRuntime;
@@ -14,17 +14,50 @@
14
14
  * `applicationVersion` stamp because all registration happens in this one call,
15
15
  * before launch — a blue/green drain treats them as a single cohort.
16
16
  */
17
+ import { err, ok } from "neverthrow";
17
18
  import { createConversationAgent } from "../agents/conversation-agent.js";
18
19
  import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
19
20
  import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
20
21
  import { registerSandboxStep } from "../workflows/sandbox-step.js";
21
22
  import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
22
23
  import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
24
+ import { createCitationResolver } from "../citations/resolve.js";
25
+ const _assertDomainError = true;
26
+ /**
27
+ * Wrap a type→agent registry as the resolution surface. Held apart from
28
+ * `assembleCoreRuntime` so the resolution contract — a registered type resolves
29
+ * to the very object the registry holds, an unregistered one refuses — is
30
+ * exercisable without the DBOS registration `assembleCoreRuntime` performs.
31
+ *
32
+ * The registry holds assembled singletons, so `forThread` returns the same
33
+ * `AgentDefinition` object on every call for a given type: construction-time
34
+ * captures (an embedder's delegating provider handles, closure-wired tools)
35
+ * stay valid across every turn.
36
+ */
37
+ export function createThreadAgentResolver(registry) {
38
+ return {
39
+ forThread: (type) => {
40
+ const agent = registry[type];
41
+ if (agent === undefined) {
42
+ const refusal = { type: "unregistered_thread_type", threadType: type };
43
+ return err(refusal);
44
+ }
45
+ return ok(agent);
46
+ },
47
+ };
48
+ }
23
49
  export function assembleCoreRuntime(deps) {
24
50
  const { conversation, workflows: wf, resourcePolicy } = deps;
25
51
  const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
26
- const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, usageRecorder });
27
- const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), usageRecorder });
52
+ const citationResolver = createCitationResolver({
53
+ ...(deps.citationResolverConfig ?? {}),
54
+ ...(deps.citationResolverConfig?.ncbiApiKey !== undefined || conversation.bioKeys.ncbi === undefined ? {} : { ncbiApiKey: conversation.bioKeys.ncbi }),
55
+ ...(deps.citationResolverConfig?.semanticScholarApiKey !== undefined || conversation.bioKeys.semanticScholar === undefined
56
+ ? {}
57
+ : { semanticScholarApiKey: conversation.bioKeys.semanticScholar }),
58
+ });
59
+ const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, citationResolver, usageRecorder });
60
+ const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), citationResolver, usageRecorder });
28
61
  const executeTargetAssessment = registerExecuteTargetAssessment({ ...wf.executeTargetAssessment, usageRecorder });
29
62
  const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder });
30
63
  const conversationAgent = createConversationAgent({
@@ -32,14 +65,25 @@ export function assembleCoreRuntime(deps) {
32
65
  executeAnalysisWorkflow: executeAnalysis,
33
66
  resourcePolicy,
34
67
  usageRecorder,
68
+ citationResolver,
35
69
  });
70
+ // The single registration point for every typed agent. One entry today; a
71
+ // future thread type's agent plugs in here at assembly with no embedder
72
+ // change. `conversation` is required at the type level because boot resolves
73
+ // it unconditionally (`boot.ts` backfill) — a dropped registration must fail
74
+ // tsc here, not surface only as a boot-time throw. The `Partial` over the
75
+ // rest keeps the compiler honest that `report` has no agent yet.
76
+ const agents = {
77
+ conversation: conversationAgent,
78
+ };
36
79
  return {
37
- conversationAgent,
80
+ agents: createThreadAgentResolver(agents),
38
81
  workflows: {
39
82
  executeAnalysis,
40
83
  sandboxStep,
41
84
  executeTargetAssessment,
42
85
  dataProfile,
43
86
  },
87
+ citationResolver,
44
88
  };
45
89
  }
@@ -11,9 +11,9 @@
11
11
  * to a no-op so a library host never acquires process-wide telemetry (or a
12
12
  * console banner) it did not ask for. An embedder that wants harness OTel
13
13
  * passes `initOtel` / `shutdownOtel` here.
14
- * 2. `validateAgentSkills(skillsDir, SANDBOX_AGENT_META)` — pure fs stat, zero
15
- * external deps. A `meta.skills` typo or a `skillsDir` / image drift dies in
16
- * milliseconds, before any Postgres or DBOS cost is paid (agent-skill-assignment).
14
+ * 2. `validateAgentSkills(skillsDir, ...)` — pure fs stat, zero external deps.
15
+ * A `meta.skills` typo or a `skillsDir` / image drift dies in milliseconds,
16
+ * before any Postgres or DBOS cost is paid (agent-skill-assignment).
17
17
  * 3. `initCortexState(pool)` — app tables must exist before launch; recovery
18
18
  * queries them on the first step.
19
19
  * 4. `assertConnectionBudget(...)` — needs the live pool; gates launch so a
@@ -11,9 +11,9 @@
11
11
  * to a no-op so a library host never acquires process-wide telemetry (or a
12
12
  * console banner) it did not ask for. An embedder that wants harness OTel
13
13
  * passes `initOtel` / `shutdownOtel` here.
14
- * 2. `validateAgentSkills(skillsDir, SANDBOX_AGENT_META)` — pure fs stat, zero
15
- * external deps. A `meta.skills` typo or a `skillsDir` / image drift dies in
16
- * milliseconds, before any Postgres or DBOS cost is paid (agent-skill-assignment).
14
+ * 2. `validateAgentSkills(skillsDir, ...)` — pure fs stat, zero external deps.
15
+ * A `meta.skills` typo or a `skillsDir` / image drift dies in milliseconds,
16
+ * before any Postgres or DBOS cost is paid (agent-skill-assignment).
17
17
  * 3. `initCortexState(pool)` — app tables must exist before launch; recovery
18
18
  * queries them on the first step.
19
19
  * 4. `assertConnectionBudget(...)` — needs the live pool; gates launch so a
@@ -36,7 +36,9 @@
36
36
  * releases whatever it acquired). Only `shutdown` swallows per-step failures —
37
37
  * once shutdown starts, exiting takes precedence (see `runShutdownSequence`).
38
38
  */
39
+ import { unwrapOrThrow } from "../lib/result.js";
39
40
  import { SANDBOX_AGENT_META } from "../agents/sandbox/index.js";
41
+ import { REPORT_BUILDER_AGENT_ID, REPORT_BUILDER_SKILLS } from "../agents/report-builder.js";
40
42
  import { validateAgentSkills } from "../agents/sandbox/validate-skills.js";
41
43
  import { initCortexState } from "../state/init.js";
42
44
  import { backfillConversationDisplayEnvelopes } from "../memory/conversation-display-backfill.js";
@@ -47,17 +49,32 @@ import { markDraining } from "./lifecycle.js";
47
49
  import { runShutdownSequence } from "./shutdown.js";
48
50
  const noop = () => { };
49
51
  const noopAsync = () => Promise.resolve();
52
+ /**
53
+ * Every agent whose declared packs must resolve before the first run. The
54
+ * report-builder is not in the sandbox catalog — it is not plannable — but it
55
+ * declares a pack all the same, and a missing one there surfaces as a failed
56
+ * render partway through a report rather than at boot.
57
+ */
58
+ const SKILL_DECLARING_AGENTS = {
59
+ ...SANDBOX_AGENT_META,
60
+ [REPORT_BUILDER_AGENT_ID]: { id: REPORT_BUILDER_AGENT_ID, skills: REPORT_BUILDER_SKILLS },
61
+ };
50
62
  export async function bootHarness(deps) {
51
63
  const { core, pool, skillsDir, logger } = deps;
52
64
  (deps.initTelemetry ?? noop)();
53
- validateAgentSkills(skillsDir, SANDBOX_AGENT_META);
65
+ validateAgentSkills(skillsDir, SKILL_DECLARING_AGENTS);
54
66
  await initCortexState(pool);
55
67
  await assertConnectionBudget({ pool, logger, config: deps.connectionBudget });
56
68
  const runtime = assembleCoreRuntime(core);
69
+ // Assembly registers `conversation` unconditionally, so this resolution can
70
+ // never refuse; `unwrapOrThrow` turns the impossible error branch into a
71
+ // boot-propagating throw rather than threading a Result through the boot
72
+ // sequence, whose steps already fail by propagation.
73
+ const conversationAgent = unwrapOrThrow(runtime.agents.forThread("conversation"));
57
74
  await backfillConversationDisplayEnvelopes({
58
75
  pool,
59
76
  resolveWorkspaceRoot: core.conversation.resolveWorkspaceRoot,
60
- tools: runtime.conversationAgent.tools,
77
+ tools: conversationAgent.tools,
61
78
  });
62
79
  await deps.beforeLaunch?.();
63
80
  await launchDbos({ config: deps.dbos, logger });
@@ -38,7 +38,8 @@ export interface DbosConfig {
38
38
  */
39
39
  readonly logLevel?: string;
40
40
  }
41
- interface State {
41
+ /** DBOS lifecycle facts a host's readiness probe reports on. */
42
+ export interface DbosState {
42
43
  launched: boolean;
43
44
  recoveryStarted: boolean;
44
45
  }
@@ -85,9 +86,8 @@ export declare function sweepEphemeralWorkflows({ pool, logger: injectedLogger,
85
86
  executorId: string;
86
87
  }): Promise<void>;
87
88
  /** Snapshot of DBOS lifecycle state — read by the readiness probe. */
88
- export declare function dbosState(): State;
89
+ export declare function dbosState(): DbosState;
89
90
  /** Test hook: force-reset state without calling DBOS. Test-only. */
90
91
  export declare function __resetDbosStateForTest(): void;
91
92
  /** Test hook: mark launched without calling DBOS. Test-only. */
92
- export declare function __setDbosStateForTest(next: Partial<State>): void;
93
- export {};
93
+ export declare function __setDbosStateForTest(next: Partial<DbosState>): void;
@@ -57,6 +57,22 @@ export interface StepArtifactRef {
57
57
  * slice of it — and so the same call replays to the same rows.
58
58
  */
59
59
  export declare function queryStepArtifactPaths(pool: Querier, resourceId: string, runId: string, stepId: string, limit: number): Promise<StepArtifactRef[]>;
60
+ /** One registered artifact of an analysis, as a reader of the full artifact set names it. */
61
+ export interface AnalysisArtifactRef {
62
+ /** Workspace-root-relative path (`runs/{runId}/{stepId}/output/de.csv`). */
63
+ path: string;
64
+ hash: string;
65
+ fileType: string | null;
66
+ }
67
+ /**
68
+ * Every artifact of one analysis, ordered by path so the same call gives the same rows.
69
+ *
70
+ * The query applies no filter. An input row is a member, the same as a step output. A row that carries
71
+ * `unrecoverable_at` is a member too, because the artifact existed at the moment of this read. If the
72
+ * query drops such a row, a later refusal says that the artifact never existed, and that reason is
73
+ * false.
74
+ */
75
+ export declare function queryAnalysisArtifacts(pool: Querier, analysisId: string): Promise<AnalysisArtifactRef[]>;
60
76
  /** Count step-output artifacts produced by a run — for the run-completed card. */
61
77
  export declare function countArtifactsForRun(pool: Querier, analysisId: string, runId: string): Promise<number>;
62
78
  export declare function updateArtifactId(pool: Querier, resourceId: string, path: string, artifactId: string, fileType?: string | null): Promise<void>;
@@ -148,6 +148,24 @@ export async function queryStepArtifactPaths(pool, resourceId, runId, stepId, li
148
148
  });
149
149
  return result.rows.map((r) => ({ path: r.path, fileType: r.file_type ?? null }));
150
150
  }
151
+ /**
152
+ * Every artifact of one analysis, ordered by path so the same call gives the same rows.
153
+ *
154
+ * The query applies no filter. An input row is a member, the same as a step output. A row that carries
155
+ * `unrecoverable_at` is a member too, because the artifact existed at the moment of this read. If the
156
+ * query drops such a row, a later refusal says that the artifact never existed, and that reason is
157
+ * false.
158
+ */
159
+ export async function queryAnalysisArtifacts(pool, analysisId) {
160
+ const result = await pool.query({
161
+ text: `SELECT path, hash, file_type
162
+ FROM cortex_artifacts
163
+ WHERE analysis_id = $1
164
+ ORDER BY path`,
165
+ values: [analysisId],
166
+ });
167
+ return result.rows.map((r) => ({ path: r.path, hash: r.hash, fileType: r.file_type ?? null }));
168
+ }
151
169
  /** Count step-output artifacts produced by a run — for the run-completed card. */
152
170
  export async function countArtifactsForRun(pool, analysisId, runId) {
153
171
  const result = await pool.query({
@@ -130,6 +130,37 @@ CREATE TABLE IF NOT EXISTS cortex_plans (
130
130
  CREATE INDEX IF NOT EXISTS idx_cortex_plans_analysis
131
131
  ON cortex_plans(analysis_id, created_at DESC);
132
132
 
133
+ -- Report versions — one immutable row for each recorded report version. A row
134
+ -- holds the block document, the pinned snapshot, and the anchor. A thread holds
135
+ -- at most one version. The named unique constraint on thread_id enforces the
136
+ -- rule, because the report policy binds one version to each report session. The
137
+ -- row outlives its thread. Thus the table has no foreign key to
138
+ -- cortex_analysis_threads, and the row carries the anchor columns (thread_id,
139
+ -- parent_thread_id, parent_seq) directly. A purge of the analysis removes the row
140
+ -- through the analysis_id cascade. The table is append-only, and a correction is
141
+ -- a new version, never an update to a recorded row.
142
+ CREATE TABLE IF NOT EXISTS cortex_report_versions (
143
+ version_id TEXT PRIMARY KEY,
144
+ analysis_id TEXT NOT NULL
145
+ REFERENCES cortex_analysis_state(analysis_id)
146
+ ON DELETE CASCADE,
147
+ thread_id TEXT NOT NULL,
148
+ -- Nullable pair: a root conversation anchor can be absent. A version with no
149
+ -- parent thread carries no parent_seq either.
150
+ parent_thread_id TEXT,
151
+ parent_seq BIGINT,
152
+ parent_version_id TEXT
153
+ REFERENCES cortex_report_versions(version_id)
154
+ ON DELETE SET NULL,
155
+ document JSONB NOT NULL,
156
+ snapshot JSONB NOT NULL,
157
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
158
+ CONSTRAINT cortex_report_versions_one_per_thread UNIQUE (thread_id)
159
+ );
160
+
161
+ CREATE INDEX IF NOT EXISTS idx_cortex_report_versions_analysis
162
+ ON cortex_report_versions(analysis_id);
163
+
133
164
  -- Target assessments — organization-scoped, snapshot-style target dossiers.
134
165
  -- Independent of analyses, projects, runs, and chat threads. The full
135
166
  -- dossier is persisted as JSONB on the row, no separate artifacts table.
@@ -275,12 +306,57 @@ CREATE TABLE IF NOT EXISTS cortex_analysis_threads (
275
306
  title TEXT,
276
307
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
277
308
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
278
- deleted_at TIMESTAMPTZ
309
+ deleted_at TIMESTAMPTZ,
310
+ thread_type TEXT NOT NULL DEFAULT 'conversation',
311
+ -- The cascade on this self-reference is a BACKSTOP, never the deletion path.
312
+ -- messages carries no foreign key to this table, so a cascade firing on its
313
+ -- own drops a descendant's metadata row while leaving that descendant's
314
+ -- messages rows behind with nothing naming them, beyond the reach of any
315
+ -- later thread-scoped or analysis-scoped reclamation. purgeThread therefore
316
+ -- walks the subtree and deletes its messages rows explicitly, to the same
317
+ -- depth the cascade reaches.
318
+ parent_thread_id TEXT REFERENCES cortex_analysis_threads(thread_id) ON DELETE CASCADE,
319
+ parent_seq BIGINT
279
320
  );
280
321
 
322
+ -- These three sit here, and not in the additive block further down, because the
323
+ -- index below reads one of them. CREATE TABLE IF NOT EXISTS adds no column to a
324
+ -- table that already exists, so on every database but a brand new one the column
325
+ -- arrives from an ALTER -- and the whole additive block runs after this text. An
326
+ -- index declared here over a column added there fails with 42703 on exactly the
327
+ -- databases a migration exists to serve. Additive with no backfill either way: an
328
+ -- existing row reads back as a root conversation thread with no anchor, which is
329
+ -- what it is.
330
+ ALTER TABLE cortex_analysis_threads
331
+ ADD COLUMN IF NOT EXISTS thread_type TEXT NOT NULL DEFAULT 'conversation';
332
+
333
+ ALTER TABLE cortex_analysis_threads
334
+ ADD COLUMN IF NOT EXISTS parent_thread_id TEXT REFERENCES cortex_analysis_threads(thread_id) ON DELETE CASCADE;
335
+
336
+ ALTER TABLE cortex_analysis_threads
337
+ ADD COLUMN IF NOT EXISTS parent_seq BIGINT;
338
+
281
339
  CREATE INDEX IF NOT EXISTS idx_cortex_analysis_threads_analysis
282
340
  ON cortex_analysis_threads(analysis_id) WHERE deleted_at IS NULL;
283
341
 
342
+ -- Not partial, unlike the analysis index above. Two of the three readers of
343
+ -- parent_thread_id carry no deleted_at predicate and cannot: the recursive
344
+ -- subtree walk in thread-store.ts must reach archived descendants, and the
345
+ -- referential trigger behind ON DELETE CASCADE is the database's own query.
346
+ -- Postgres uses a partial index only where it can prove the predicate holds, so
347
+ -- a partial index here leaves both to a sequential scan of the whole table.
348
+ -- Over 150k rows that measured 28 ms per walk and 20 ms per cascade, against
349
+ -- 0.06 ms and 0.10 ms with this one. The third reader, a listing filtered by
350
+ -- parent, keeps an index scan either way.
351
+ --
352
+ -- The old partial index goes by name: CREATE INDEX IF NOT EXISTS keeps whatever
353
+ -- definition already holds the name, so a database that built the partial one
354
+ -- would never see this.
355
+ DROP INDEX IF EXISTS idx_cortex_analysis_threads_parent;
356
+
357
+ CREATE INDEX IF NOT EXISTS idx_cortex_analysis_threads_parent_fk
358
+ ON cortex_analysis_threads(parent_thread_id);
359
+
284
360
  -- Working memory — the harness WorkingMemory module's table, one row per
285
361
  -- analysis. The data column holds a JSONB object with four sections (goal,
286
362
  -- constraints, hypotheses, findings). Section updates are a read-modify-write
@@ -80,6 +80,7 @@ const ANALYSIS_KEYED_DELETES = [
80
80
  { op: "purgeAnalysis.asks", sql: "DELETE FROM cortex_asks WHERE analysis_id = $1" },
81
81
  { op: "purgeAnalysis.askGrants", sql: "DELETE FROM cortex_ask_grants WHERE analysis_id = $1" },
82
82
  { op: "purgeAnalysis.plans", sql: "DELETE FROM cortex_plans WHERE analysis_id = $1" },
83
+ { op: "purgeAnalysis.reportVersions", sql: "DELETE FROM cortex_report_versions WHERE analysis_id = $1" },
83
84
  { op: "purgeAnalysis.analysisState", sql: "DELETE FROM cortex_analysis_state WHERE analysis_id = $1" },
84
85
  ];
85
86
  /**
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The report version store -- the append-only record of report versions.
3
+ *
4
+ * A version is one immutable row. It holds the block document, the pinned
5
+ * snapshot, and the anchor. The store records a version and reads a version. It
6
+ * never updates a recorded row, thus a correction is a new version.
7
+ *
8
+ * A thread holds at most one version, and a named unique constraint on the thread
9
+ * id enforces it. The record reads nothing before the insert. A second record for
10
+ * one thread trips the constraint, and the store maps that trip to the typed
11
+ * refusal thread_already_holds_version.
12
+ *
13
+ * The store keeps the snapshot and the anchor as given, and it never mints a
14
+ * snapshot. A read parses the stored document and the stored snapshot with the
15
+ * current schemas. A row that fails a parse reads as a typed error, and it does
16
+ * not crash. An absent row reads as a normal absence.
17
+ *
18
+ * A version outlives its thread. The anchor columns sit on the row, thus a
19
+ * deleted thread leaves a recorded version in place. Only a purge of the analysis
20
+ * removes the version, through the analysis-id cascade.
21
+ */
22
+ import { type ResultAsync } from "neverthrow";
23
+ import type { Pool } from "pg";
24
+ import { type ReportDocument } from "../contracts/report-blocks.js";
25
+ import { type DbError } from "../lib/db-result.js";
26
+ import type { Logger } from "../lib/logger.js";
27
+ import type { ReportSnapshot } from "../report-model/reference-resolver.js";
28
+ /** One reduced schema issue -- the dotted path and the message, without the rest. */
29
+ export interface SchemaIssue {
30
+ readonly path: string;
31
+ readonly message: string;
32
+ }
33
+ /** The anchor and the payload of a version to record. */
34
+ export interface RecordVersionInput {
35
+ /** The block document. It rides as `unknown`, and the record parses it. */
36
+ readonly document: unknown;
37
+ /** The pinned snapshot, stored as given. */
38
+ readonly snapshot: ReportSnapshot;
39
+ readonly analysisId: string;
40
+ readonly threadId: string;
41
+ /** The parent conversation thread of the anchor, or `null` when a root has none. */
42
+ readonly parentThreadId: string | null;
43
+ /** The parent transcript position of the anchor, or `null` beside a null parent thread. */
44
+ readonly parentSeq: number | null;
45
+ /** The earlier version that this one reuses. The parent must belong to the same analysis. */
46
+ readonly parentVersionId?: string;
47
+ }
48
+ /** The stable reference of a freshly recorded version. */
49
+ export interface RecordedVersionRef {
50
+ readonly versionId: string;
51
+ }
52
+ /**
53
+ * A record the store refuses as data, before or in place of a row. Distinct from
54
+ * `DbError`: the request describes a version that the store will not write.
55
+ *
56
+ * The store parses the document and the snapshot before any row. The store
57
+ * refuses a malformed document or a malformed snapshot here as typed data, and
58
+ * no row lands.
59
+ *
60
+ * A second record for one thread is `thread_already_holds_version`. The unique
61
+ * constraint on the thread id trips, and the record maps that one constraint by
62
+ * its name. Every other constraint trip stays a `DbError`.
63
+ *
64
+ * A `parentVersionId` naming no row is not one of these -- the self foreign key
65
+ * refuses an unknown parent id, and `tryMutation` classifies that refusal as the
66
+ * `constraint_violation` variant of `DbError`.
67
+ */
68
+ export type RecordVersionError = {
69
+ readonly type: "malformed_document";
70
+ readonly op: string;
71
+ readonly issues: readonly SchemaIssue[];
72
+ } | {
73
+ readonly type: "malformed_snapshot";
74
+ readonly op: string;
75
+ readonly issues: readonly SchemaIssue[];
76
+ } | {
77
+ readonly type: "parent_analysis_mismatch";
78
+ readonly op: string;
79
+ readonly analysisId: string;
80
+ readonly parentVersionId: string;
81
+ readonly parentAnalysisId: string;
82
+ } | {
83
+ readonly type: "thread_already_holds_version";
84
+ readonly op: string;
85
+ readonly threadId: string;
86
+ } | DbError;
87
+ /**
88
+ * A stored row that a read cannot parse with the current schemas. It names the
89
+ * version id and the part that failed, and it carries the reduced issues.
90
+ */
91
+ export type VersionReadError = {
92
+ readonly type: "corrupt_version";
93
+ readonly op: string;
94
+ readonly versionId: string;
95
+ readonly part: "document" | "snapshot";
96
+ readonly issues: readonly SchemaIssue[];
97
+ };
98
+ /** One recorded version, as the store gives it back. */
99
+ export interface RecordedVersion {
100
+ readonly versionId: string;
101
+ readonly analysisId: string;
102
+ readonly threadId: string;
103
+ readonly parentThreadId: string | null;
104
+ readonly parentSeq: number | null;
105
+ /** The earlier version that this one reuses, or `null` when it has none. */
106
+ readonly parentVersionId: string | null;
107
+ readonly document: ReportDocument;
108
+ readonly snapshot: ReportSnapshot;
109
+ readonly createdAt: Date;
110
+ }
111
+ export interface ReportVersionStore {
112
+ /**
113
+ * Record a version. The record parses the document, refuses a malformed value
114
+ * as data with no row, and refuses a parent from a different analysis. It reads
115
+ * nothing before the insert. A second record for one thread refuses with
116
+ * thread_already_holds_version. The snapshot and the anchor store as given.
117
+ */
118
+ record(input: RecordVersionInput): ResultAsync<RecordedVersionRef, RecordVersionError>;
119
+ /** One version by its id, or `null` when no row holds it. */
120
+ getVersion(versionId: string): ResultAsync<RecordedVersion | null, DbError | VersionReadError>;
121
+ /** The one version of a thread, or `null` for a thread with no version. */
122
+ getThreadVersion(threadId: string): ResultAsync<RecordedVersion | null, DbError | VersionReadError>;
123
+ }
124
+ export interface ReportVersionStoreDeps {
125
+ readonly pool: Pool;
126
+ readonly logger?: Logger;
127
+ }
128
+ /**
129
+ * Create a `ReportVersionStore` bound to a Postgres pool. The
130
+ * `cortex_report_versions` table is provisioned by the state-init DDL.
131
+ */
132
+ export declare function createReportVersionStore({ pool }: ReportVersionStoreDeps): ReportVersionStore;