@inflexa-ai/harness 0.17.0 → 0.19.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 (249) hide show
  1. package/dist/agents/report-builder.d.ts +2 -0
  2. package/dist/agents/report-builder.js +2 -0
  3. package/dist/agents/report-session-agent.d.ts +56 -0
  4. package/dist/agents/report-session-agent.js +72 -0
  5. package/dist/app/report-session-runtime.d.ts +72 -0
  6. package/dist/app/report-session-runtime.js +157 -0
  7. package/dist/app/spawn-report-session.d.ts +85 -0
  8. package/dist/app/spawn-report-session.js +83 -0
  9. package/dist/contracts/chat-events.d.ts +9 -0
  10. package/dist/contracts/index.d.ts +6 -2
  11. package/dist/contracts/index.js +3 -1
  12. package/dist/contracts/organ-system.d.ts +49 -0
  13. package/dist/contracts/organ-system.js +67 -0
  14. package/dist/contracts/report-blocks.d.ts +564 -0
  15. package/dist/contracts/report-blocks.js +117 -0
  16. package/dist/contracts/report-reference.d.ts +459 -0
  17. package/dist/contracts/report-reference.js +256 -0
  18. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  19. package/dist/contracts/schemas/chat-events.js +2 -0
  20. package/dist/contracts/severity.d.ts +15 -0
  21. package/dist/contracts/severity.js +10 -0
  22. package/dist/contracts/target-dossier.d.ts +8675 -12972
  23. package/dist/contracts/target-dossier.js +534 -591
  24. package/dist/data/safety-panel-schema.d.ts +18 -5
  25. package/dist/data/safety-panel-schema.js +4 -4
  26. package/dist/execution/artifact-metadata.js +1 -0
  27. package/dist/execution/report-runner.js +1 -3
  28. package/dist/execution/run-synthesis.js +2 -0
  29. package/dist/index.d.ts +6 -0
  30. package/dist/index.js +19 -0
  31. package/dist/lib/async-utils.d.ts +16 -4
  32. package/dist/lib/async-utils.js +20 -15
  33. package/dist/lib/host-concurrency.js +1 -0
  34. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  35. package/dist/loop/run-agent.js +58 -10
  36. package/dist/loop/tool-detail.d.ts +40 -0
  37. package/dist/loop/tool-detail.js +35 -3
  38. package/dist/loop/types.d.ts +13 -0
  39. package/dist/memory/thread-history.d.ts +10 -0
  40. package/dist/memory/thread-history.js +12 -1
  41. package/dist/memory/thread-store.d.ts +4 -4
  42. package/dist/memory/thread-store.js +18 -9
  43. package/dist/prompts/report-session.d.ts +1 -0
  44. package/dist/prompts/report-session.js +89 -0
  45. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  46. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  47. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  48. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  49. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  50. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  51. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  52. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  53. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  54. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  55. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  56. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  57. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  58. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  59. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  60. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  61. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  62. package/dist/providers/__fixtures__/session.d.ts +5 -4
  63. package/dist/providers/__fixtures__/session.js +4 -4
  64. package/dist/providers/ai-sdk.d.ts +77 -0
  65. package/dist/providers/ai-sdk.js +149 -19
  66. package/dist/providers/errors.d.ts +14 -0
  67. package/dist/providers/errors.js +103 -18
  68. package/dist/providers/types.d.ts +16 -0
  69. package/dist/providers/types.js +10 -1
  70. package/dist/report-model/block-walk.d.ts +56 -0
  71. package/dist/report-model/block-walk.js +89 -0
  72. package/dist/report-model/draft-finish.d.ts +60 -0
  73. package/dist/report-model/draft-finish.js +56 -0
  74. package/dist/report-model/draft-operations.d.ts +125 -0
  75. package/dist/report-model/draft-operations.js +341 -0
  76. package/dist/report-model/draft-read.d.ts +59 -0
  77. package/dist/report-model/draft-read.js +109 -0
  78. package/dist/report-model/draft.d.ts +51 -0
  79. package/dist/report-model/draft.js +32 -0
  80. package/dist/report-model/fixture-resolver.d.ts +8 -0
  81. package/dist/report-model/fixture-resolver.js +252 -0
  82. package/dist/report-model/pin-snapshot.d.ts +30 -0
  83. package/dist/report-model/pin-snapshot.js +40 -0
  84. package/dist/report-model/reference-resolver.d.ts +113 -0
  85. package/dist/report-model/reference-resolver.js +60 -0
  86. package/dist/report-model/structural-validation.d.ts +22 -0
  87. package/dist/report-model/structural-validation.js +68 -0
  88. package/dist/report-model/validate.d.ts +72 -0
  89. package/dist/report-model/validate.js +95 -0
  90. package/dist/report-render/chart.d.ts +33 -0
  91. package/dist/report-render/chart.js +538 -0
  92. package/dist/report-render/page.d.ts +127 -0
  93. package/dist/report-render/page.js +307 -0
  94. package/dist/report-render/references.d.ts +32 -0
  95. package/dist/report-render/references.js +44 -0
  96. package/dist/report-render/render.d.ts +25 -0
  97. package/dist/report-render/render.js +125 -0
  98. package/dist/report-render/script-json.d.ts +13 -0
  99. package/dist/report-render/script-json.js +15 -0
  100. package/dist/report-render/types.d.ts +39 -0
  101. package/dist/report-render/types.js +10 -0
  102. package/dist/report-render/value-bridge.d.ts +62 -0
  103. package/dist/report-render/value-bridge.js +82 -0
  104. package/dist/report-render/views/chart-view.d.ts +11 -0
  105. package/dist/report-render/views/chart-view.js +23 -0
  106. package/dist/report-render/views/page-view.d.ts +20 -0
  107. package/dist/report-render/views/page-view.js +49 -0
  108. package/dist/report-render/views/prose.d.ts +28 -0
  109. package/dist/report-render/views/prose.js +75 -0
  110. package/dist/report-render/views/references-view.d.ts +20 -0
  111. package/dist/report-render/views/references-view.js +64 -0
  112. package/dist/report-render/views/values.d.ts +42 -0
  113. package/dist/report-render/views/values.js +43 -0
  114. package/dist/runtime/assemble.d.ts +15 -0
  115. package/dist/runtime/assemble.js +28 -6
  116. package/dist/runtime/boot.d.ts +3 -3
  117. package/dist/runtime/boot.js +15 -4
  118. package/dist/runtime/dbos.d.ts +4 -4
  119. package/dist/state/artifacts.d.ts +16 -0
  120. package/dist/state/artifacts.js +18 -0
  121. package/dist/state/init.js +54 -0
  122. package/dist/state/purge-analysis.js +2 -0
  123. package/dist/state/report-session-state.d.ts +112 -0
  124. package/dist/state/report-session-state.js +150 -0
  125. package/dist/state/report-versions.d.ts +129 -0
  126. package/dist/state/report-versions.js +167 -0
  127. package/dist/state/snapshot-parse.d.ts +34 -0
  128. package/dist/state/snapshot-parse.js +82 -0
  129. package/dist/tasks/data-profile.js +1 -0
  130. package/dist/tools/ad-hoc-router.d.ts +7 -2
  131. package/dist/tools/ad-hoc-router.js +2 -1
  132. package/dist/tools/bio/chembl.js +1 -0
  133. package/dist/tools/bio/comptox.js +1 -0
  134. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  135. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  136. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  137. package/dist/tools/bio/keys.d.ts +3 -2
  138. package/dist/tools/bio/keys.js +3 -2
  139. package/dist/tools/bio/lookup-annotation.js +1 -0
  140. package/dist/tools/bio/opentargets.d.ts +3 -3
  141. package/dist/tools/bio/opentargets.js +4 -3
  142. package/dist/tools/bio/pubchem.js +1 -0
  143. package/dist/tools/bio/search-clinical-trials.js +1 -0
  144. package/dist/tools/bio/search-faers.js +1 -0
  145. package/dist/tools/bio/search-geo-datasets.js +1 -0
  146. package/dist/tools/bio/search-interactions.js +1 -0
  147. package/dist/tools/bio/target-safety.d.ts +4 -6
  148. package/dist/tools/bio/target-safety.js +7 -6
  149. package/dist/tools/define-tool.d.ts +19 -12
  150. package/dist/tools/define-tool.js +4 -2
  151. package/dist/tools/display/show-user.js +1 -0
  152. package/dist/tools/iterate-report.js +2 -0
  153. package/dist/tools/lib/chembl-client.d.ts +2 -0
  154. package/dist/tools/lib/chembl-client.js +1 -0
  155. package/dist/tools/lib/monarch-client.d.ts +42 -0
  156. package/dist/tools/lib/monarch-client.js +100 -0
  157. package/dist/tools/report/build-report.js +1 -0
  158. package/dist/tools/report/mint-preview-url.js +1 -0
  159. package/dist/tools/report/preview-snapshot.js +1 -0
  160. package/dist/tools/report/submit-report.js +1 -0
  161. package/dist/tools/report/version-fs.js +4 -0
  162. package/dist/tools/report-authoring/authoring-tools.d.ts +241 -0
  163. package/dist/tools/report-authoring/authoring-tools.js +460 -0
  164. package/dist/tools/report-session/index.d.ts +8 -0
  165. package/dist/tools/report-session/index.js +8 -0
  166. package/dist/tools/report-session/preview-report.d.ts +90 -0
  167. package/dist/tools/report-session/preview-report.js +245 -0
  168. package/dist/tools/research/context7-docs.js +2 -0
  169. package/dist/tools/research/generate-analogy-report.js +1 -0
  170. package/dist/tools/research/generate-plan.js +7 -2
  171. package/dist/tools/research/inspect-data-profile.js +4 -0
  172. package/dist/tools/research/literature-reviewer.js +1 -0
  173. package/dist/tools/research/search-arxiv.js +1 -0
  174. package/dist/tools/research/search-github-repos.js +1 -0
  175. package/dist/tools/research/search-semantic-scholar.js +1 -0
  176. package/dist/tools/sandbox/list-available-packages.js +23 -0
  177. package/dist/tools/sandbox/list-available-refs.js +36 -0
  178. package/dist/tools/sandbox/report-blocker.js +1 -0
  179. package/dist/tools/sandbox/skills.js +2 -0
  180. package/dist/tools/workspace/file-stat.js +1 -0
  181. package/dist/tools/workspace/show-file.js +2 -0
  182. package/dist/tools/workspace/show-plan.js +1 -0
  183. package/dist/workflows/execute-analysis.d.ts +5 -1
  184. package/dist/workflows/execute-analysis.js +5 -5
  185. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  186. package/dist/workflows/execute-target-assessment.js +78 -16
  187. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  188. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  189. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  190. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  191. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  192. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  193. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  194. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  195. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  196. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  197. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  198. package/dist/workflows/target-assessment/coverage.js +33 -29
  199. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  200. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  201. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  202. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  203. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  204. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  205. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  206. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  207. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  208. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  209. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  210. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  211. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  212. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  213. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  214. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  215. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  216. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  217. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  218. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  219. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  220. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  221. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  222. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  223. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  224. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  225. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  226. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  227. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  228. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  229. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  230. package/dist/workflows/target-assessment/metrics.js +1 -13
  231. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
  232. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  233. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  234. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  235. package/dist/workflows/target-assessment/schemas.d.ts +46 -1
  236. package/dist/workflows/target-assessment/schemas.js +19 -0
  237. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  238. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  239. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
  240. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  241. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
  242. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  243. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  244. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  245. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  246. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  247. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  248. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  249. package/package.json +4 -1
@@ -6,13 +6,16 @@
6
6
  *
7
7
  * Phase 0 `phase0Resolve` — only step allowed to throw
8
8
  * (terminates → `target-unresolved`)
9
- * Phase 1 14 collectors in parallel — coverage-envelope failures
9
+ * Phase 1 15 collectors in parallel — coverage-envelope failures
10
10
  * Progress `emitProgress("deciding")`
11
11
  * Phase 2 2 decisions in parallel — LLM calls, attempt-numbered
12
12
  * Progress `emitProgress("fanning_out")`
13
13
  * Phase 3 4 fan-out blocks in parallel — per-item HTTP, withHost semaphore
14
14
  * Progress `emitProgress("assembling")`
15
- * Phase 4 `phase4Assemble` — deterministic
15
+ * Phase 4 `phase4Assemble` — aggregation + cached annotator
16
+ * Phase 4a `ta-approval-precedents` — one openFDA lookup, two projections
17
+ * Phase 4b `assembleSafetyCorroboration` — pure fold, no step of its own
18
+ * Phase 4c `investigateClaims` — propose / critique / re-verify / converge
16
19
  * Progress `emitProgress("synthesizing")`
17
20
  * Phase 5a 3 per-section syntheses in parallel
18
21
  * Phase 5b `dossierRecommendation` sequential
@@ -35,14 +38,19 @@ import { z } from "zod";
35
38
  import { forSubAgent } from "../auth/types.js";
36
39
  import { createNoopLogger } from "../lib/console-logger.js";
37
40
  import { unwrapOrThrow } from "../lib/result.js";
41
+ import { durableStep } from "../loop/run-step.js";
42
+ import { createPubMedTool } from "../tools/bio/pubmed.js";
38
43
  import { resolveTarget } from "../tools/lib/identifier-resolver.js";
39
44
  import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
40
45
  import { getAssessment, markAssessmentSuspended, markFailed, setDossier } from "../state/target-assessments.js";
41
46
  import { COLLECTOR_MANIFEST } from "./target-assessment/collectors/index.js";
42
47
  import { drugsInClass, modulatorTriage } from "./target-assessment/decisions/index.js";
43
48
  import { aesForOneClassDrug, aesForOneTrial, faersForOneModulator, polypharmForOneModulator, } from "./target-assessment/fanout/index.js";
44
- import { fetchApprovalPrecedents, pickIndicationForPrecedents, renderApprovalPrecedents } from "./target-assessment/lib/approval-precedents.js";
49
+ import { fetchApprovalPrecedents, pickIndicationForPrecedents, precedentLabelSafety, renderApprovalPrecedents, } from "./target-assessment/lib/approval-precedents.js";
50
+ import { segmentLabelSafety } from "./target-assessment/lib/fda-label-safety.js";
45
51
  import { readBudgetExceededMarker } from "./target-assessment/lib/llm-step.js";
52
+ import { assembleSafetyCorroboration } from "./target-assessment/lib/safety-corroboration.js";
53
+ import { investigateClaims, isClaimInvestigationBudgetExceeded } from "./target-assessment/investigation/index.js";
46
54
  import { recordTerminalReason } from "./target-assessment/metrics.js";
47
55
  import { phase4Assemble } from "./target-assessment/phase4-assemble.js";
48
56
  import { DossierDerivedInvariantError, DossierSchemaViolationError, phase5Persist } from "./target-assessment/phase5-persist.js";
@@ -151,7 +159,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
151
159
  phase0Error = err;
152
160
  throw err;
153
161
  });
154
- // (§5.4) Phase 1 — 14 collectors in parallel. Each is its own DBOS
162
+ // (§5.4) Phase 1 — 15 collectors in parallel. Each is its own DBOS
155
163
  // step; coverage failures cache as `queried_no_data` and replay
156
164
  // returns the cached envelope. The per-collector run functions emit
157
165
  // different bundle shapes; the union is widened to `unknown` here
@@ -177,6 +185,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
177
185
  clinvar: collectorById["clinvar"],
178
186
  cbioportal: collectorById["cbioportal"],
179
187
  impc: collectorById["impc"],
188
+ monarch: collectorById["monarch"],
180
189
  pubmedIndex: collectorById["pubmed-index"],
181
190
  pathways: collectorById["pathways"],
182
191
  stringPpi: collectorById["string-ppi"],
@@ -288,14 +297,25 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
288
297
  perClassDrugAes: { results: perClassDrugAesResults },
289
298
  },
290
299
  };
291
- // (§5.7) Phase 4 — deterministic assembly. Single DBOS step.
300
+ // (§5.7) Phase 4 — assembly. Single DBOS step. The annotator seam lets
301
+ // the off-target panel's clinical-consequence pass reach its LLM on a
302
+ // cache miss; per-row failures leave the row's fallback in place.
292
303
  await emitProgress(deps.pool, logger, input.assessmentId, "assembling");
293
- const phase4 = await DBOS.runStep(() => phase4Assemble(deps.pool, phase3), { name: "ta-phase4-assemble" });
304
+ const annotatorDeps = {
305
+ provider: deps.chatProvider,
306
+ session: buildSession(input, "off-target-annotator"),
307
+ model: deps.decisionModel,
308
+ ...(deps.usageRecorder ? { usageRecorder: deps.usageRecorder } : {}),
309
+ };
310
+ const phase4 = await DBOS.runStep(() => phase4Assemble(deps.pool, phase3, annotatorDeps), {
311
+ name: "ta-phase4-assemble",
312
+ });
294
313
  // (§5.8-pre) Approval-precedent grounding — one deterministic openFDA
295
- // lookup, rendered to markdown and injected into every synthesis prompt.
296
- // A precedent-lookup failure must NOT fail the whole assessment: a fetch
297
- // throw degrades to a "no precedents" block.
298
- const approvalPrecedents = await DBOS.runStep(async () => {
314
+ // lookup feeding two projections of the same typed labels: the markdown
315
+ // block every synthesis prompt receives, and the per-organ regulatory
316
+ // signals the dossier carries. A precedent-lookup failure must NOT fail
317
+ // the whole assessment: a fetch throw degrades to a "no precedents" block.
318
+ const precedentGrounding = await DBOS.runStep(async () => {
299
319
  const indication = pickIndicationForPrecedents(phase4.dossier);
300
320
  let result = null;
301
321
  if (indication !== null) {
@@ -307,8 +327,49 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
307
327
  result = null;
308
328
  }
309
329
  }
310
- return renderApprovalPrecedents(indication, result);
330
+ return {
331
+ block: renderApprovalPrecedents(indication, result),
332
+ // A resolved indication was queried even when the lookup
333
+ // degraded to nothing; only an unresolved one was not.
334
+ organSignals: indication === null ? null : segmentLabelSafety(precedentLabelSafety(result?.precedents ?? [])),
335
+ };
311
336
  }, { name: "ta-approval-precedents" });
337
+ const approvalPrecedents = precedentGrounding.block;
338
+ // (§5.8-pre) The corroboration fold. It reads the label signals segmented
339
+ // above, so it runs here rather than at assembly — and here rather than at
340
+ // persist, because the claim investigation below is its consumer. The fold
341
+ // is pure over two cached step outputs, so it needs no step of its own: a
342
+ // replay recomputes the same record without a durable write.
343
+ const corroboration = assembleSafetyCorroboration({
344
+ phase1,
345
+ regulatoryOrganSignals: precedentGrounding.organSignals,
346
+ });
347
+ // (§5.8-pre) The claim investigation — propose a mechanism, argue
348
+ // against it, re-verify, converge. Runs before synthesis so the
349
+ // synthesis prompts read verdicts rather than bare agreement.
350
+ const investigation = await investigateClaims({ corroboration, dossier: phase4.dossier }, {
351
+ logger,
352
+ chatProvider: deps.chatProvider,
353
+ session: buildSession(input, "ta-claim-investigation"),
354
+ model: deps.synthesisModel,
355
+ attempt: 0,
356
+ runStep: durableStep,
357
+ critiqueTools: [createPubMedTool({ ...(deps.ncbiApiKey ? { ncbiApiKey: deps.ncbiApiKey } : {}) })],
358
+ ...(deps.claimInvestigation ? { config: deps.claimInvestigation } : {}),
359
+ ...(deps.usageRecorder ? { usageRecorder: deps.usageRecorder } : {}),
360
+ });
361
+ if (isClaimInvestigationBudgetExceeded(investigation)) {
362
+ budgetExceeded = true;
363
+ throw new BudgetExceededSkip();
364
+ }
365
+ const phase4Investigated = {
366
+ ...phase4,
367
+ dossier: {
368
+ ...phase4.dossier,
369
+ safety_corroboration: corroboration,
370
+ claim_investigation: investigation,
371
+ },
372
+ };
312
373
  // (§5.8) Phase 5 — three per-section syntheses in parallel.
313
374
  await emitProgress(deps.pool, logger, input.assessmentId, "synthesizing");
314
375
  const synthesisDeps = (agentId) => ({
@@ -319,9 +380,9 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
319
380
  approvalPrecedents,
320
381
  });
321
382
  const [bulletsRes, flagsRes, commentaryRes] = await Promise.all([
322
- liabilityBullets(phase4, synthesisDeps("liability-bullets")),
323
- safetyFlagsTrail(phase4, synthesisDeps("safety-flags-trail")),
324
- translationalCommentary(phase4, synthesisDeps("translational-commentary")),
383
+ liabilityBullets(phase4Investigated, synthesisDeps("liability-bullets")),
384
+ safetyFlagsTrail(phase4Investigated, synthesisDeps("safety-flags-trail")),
385
+ translationalCommentary(phase4Investigated, synthesisDeps("translational-commentary")),
325
386
  ]);
326
387
  if (isBudgetCancel(bulletsRes) || isBudgetCancel(flagsRes) || isBudgetCancel(commentaryRes)) {
327
388
  budgetExceeded = true;
@@ -329,7 +390,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
329
390
  }
330
391
  // (§5.9) Phase 5b — sequential dossier-recommendation step.
331
392
  const recommendationRes = await dossierRecommendation({
332
- phase4,
393
+ phase4: phase4Investigated,
333
394
  perSection: {
334
395
  liabilityBullets: bulletsRes,
335
396
  safetyFlagsTrail: flagsRes,
@@ -347,8 +408,9 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
347
408
  const persisted = await DBOS.runStep(() => phase5Persist({
348
409
  logger,
349
410
  assessmentId: input.assessmentId,
350
- phase4Dossier: phase4.dossier,
411
+ phase4Dossier: phase4Investigated.dossier,
351
412
  phase2,
413
+ regulatoryOrganSignals: precedentGrounding.organSignals,
352
414
  synthesis: {
353
415
  bullets: bulletsRes,
354
416
  flags: flagsRes,
@@ -1,5 +1,5 @@
1
1
  export { assembleEntity, assembleTractability, enrichTractabilityModalitiesWithApprovals, type DrugForTractability, reconcileFaersCoverage, assembleDossier, } from "./orchestrator.js";
2
2
  export type { TrialOutcomeFilter } from "./trials.js";
3
- export { assembleLiabilitySummary, isSafetyRelevant, meetsTpmFloor, SAFETY_RELEVANT_ORGANS, REPRODUCTIVE_TOX_TISSUES, REPRODUCTIVE_TOX_TPM_FLOOR, DEFAULT_TPM_FLOOR, aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, buildOrganRollup, } from "./safety.js";
3
+ export { assembleLiabilitySummary, isSafetyRelevant, meetsTpmFloor, SAFETY_RELEVANT_ORGANS, REPRODUCTIVE_TOX_TISSUES, REPRODUCTIVE_TOX_TPM_FLOOR, DEFAULT_TPM_FLOOR, aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, assembleRegulatoryOrganSignals, buildOrganRollup, } from "./safety.js";
4
4
  export { deterministicTranslationalCommentary, isClinicalMeasurement, isSelfReference } from "./literature.js";
5
5
  export { aggregateTrialOutcomes } from "./trials.js";
@@ -1,4 +1,4 @@
1
1
  export { assembleEntity, assembleTractability, enrichTractabilityModalitiesWithApprovals, reconcileFaersCoverage, assembleDossier, } from "./orchestrator.js";
2
- export { assembleLiabilitySummary, isSafetyRelevant, meetsTpmFloor, SAFETY_RELEVANT_ORGANS, REPRODUCTIVE_TOX_TISSUES, REPRODUCTIVE_TOX_TPM_FLOOR, DEFAULT_TPM_FLOOR, aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, buildOrganRollup, } from "./safety.js";
2
+ export { assembleLiabilitySummary, isSafetyRelevant, meetsTpmFloor, SAFETY_RELEVANT_ORGANS, REPRODUCTIVE_TOX_TISSUES, REPRODUCTIVE_TOX_TPM_FLOOR, DEFAULT_TPM_FLOOR, aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, assembleRegulatoryOrganSignals, buildOrganRollup, } from "./safety.js";
3
3
  export { deterministicTranslationalCommentary, isClinicalMeasurement, isSelfReference } from "./literature.js";
4
4
  export { aggregateTrialOutcomes } from "./trials.js";
@@ -1,4 +1,4 @@
1
- import type { EvidenceItem } from "@inflexa-ai/harness/contracts/target-dossier.js";
1
+ import type { ClinicalTrialAttribution, DiscoveryTrials, EvidenceItem } from "@inflexa-ai/harness/contracts/target-dossier.js";
2
2
  import type { Phase2Bundle } from "../steps/phase2-aggregate.js";
3
3
  import type { Phase3Bundle } from "../steps/phase3-aggregate.js";
4
4
  import { HIGH_EXPRESSION_TPM_THRESHOLD } from "../lib/expression-constants.js";
@@ -102,6 +102,7 @@ export declare function assembleAdditionalEvidence(phase2: Phase2Bundle): {
102
102
  source: string;
103
103
  pmid?: string | undefined;
104
104
  doi?: string | undefined;
105
+ accession?: string | undefined;
105
106
  predicate?: string | undefined;
106
107
  score?: number | undefined;
107
108
  strength?: string | undefined;
@@ -131,7 +132,7 @@ export declare function dedupePpiPartners(partners: PpiInputPartner[]): {
131
132
  }[];
132
133
  export declare function isClinicalMeasurement(name: string): boolean;
133
134
  /**
134
- * Produce a v4 discriminated `failure_category` block. The "safety" branch
135
+ * Produce a discriminated `failure_category` block. The "safety" branch
135
136
  * uses `safety_evidence_excerpt`; all other branches use
136
137
  * `category_evidence_excerpt`.
137
138
  */
@@ -197,7 +198,6 @@ export declare function partitionTrialsByAttribution<T extends {
197
198
  related: Array<T & {
198
199
  match_confidence: "off_target";
199
200
  }>;
200
- related_receptor: string | undefined;
201
201
  }>;
202
202
  type RelevanceBasis = {
203
203
  kind: "drug_in_class_match";
@@ -246,30 +246,12 @@ export declare function classifyClinicalTrialConfidence(trial: {
246
246
  conditions?: string[];
247
247
  title: string;
248
248
  }, geneSymbol: string, knownClassDrugNames: Set<string>): "high" | "medium" | "low";
249
- export declare function assembleDiscoveryTrials(phase2: Phase2Bundle, symbol: string, attrCtx: AttributionContext, knownClassDrugNames: Set<string>): Promise<{
250
- related_target_trials?: {
251
- nct_id: string;
252
- title: string;
253
- phase: string | null;
254
- status: string;
255
- conditions: string[];
256
- start_date: string | null;
257
- completion_date: string | null;
258
- match_confidence: "off_target";
259
- }[] | undefined;
260
- related_receptor?: string | undefined;
261
- rows: {
262
- nct_id: string;
263
- title: string;
264
- phase: string | null;
265
- status: string;
266
- conditions: string[];
267
- start_date: string | undefined;
268
- completion_date: string | undefined;
269
- match_confidence: "high" | "medium" | "low";
270
- relevance_basis: RelevanceBasis;
271
- }[];
272
- } | null>;
249
+ export declare function assembleDiscoveryTrials(phase2: Phase2Bundle, symbol: string, attrCtx: AttributionContext, knownClassDrugNames: Set<string>, attachAttribution: <T extends {
250
+ nct_id: string;
251
+ }>(row: T) => T & {
252
+ attribution: ClinicalTrialAttribution;
253
+ eligible_for_toxicology_aggregation: boolean;
254
+ }): Promise<DiscoveryTrials>;
273
255
  export declare function assembleEvidenceTimeline(phase2: Phase2Bundle): {
274
256
  histogram: Record<string, number>;
275
257
  first_reported_year: number | null;
@@ -1,3 +1,4 @@
1
+ import { coverageFromFilteredRows } from "../coverage.js";
1
2
  import { classifyTrialAttribution } from "../lib/target-identity-filter.js";
2
3
  import { HIGH_EXPRESSION_TPM_THRESHOLD } from "../lib/expression-constants.js";
3
4
  export { HIGH_EXPRESSION_TPM_THRESHOLD };
@@ -525,7 +526,7 @@ const CATEGORY_PATTERNS = [
525
526
  * Match the first known failure-cause pattern against `whyStopped` and return
526
527
  * the category plus the matched substring (truncated to 200 chars). When no
527
528
  * pattern matches, falls back to "operational" with the first 200 chars of
528
- * `whyStopped` itself — the v4 schema requires the evidence excerpt to be a
529
+ * `whyStopped` itself — the schema requires the evidence excerpt to be a
529
530
  * literal substring of `why_stopped`, which holds by construction.
530
531
  */
531
532
  function classifyFailureCategory(whyStopped) {
@@ -541,7 +542,7 @@ function classifyFailureCategory(whyStopped) {
541
542
  };
542
543
  }
543
544
  /**
544
- * Produce a v4 discriminated `failure_category` block. The "safety" branch
545
+ * Produce a discriminated `failure_category` block. The "safety" branch
545
546
  * uses `safety_evidence_excerpt`; all other branches use
546
547
  * `category_evidence_excerpt`.
547
548
  */
@@ -690,14 +691,6 @@ export function deterministicTranslationalCommentary(phase2) {
690
691
  }
691
692
  return out.slice(0, 6);
692
693
  }
693
- /**
694
- * Map from UniProt accession to HGNC gene symbol for related-receptor labeling.
695
- * Extend as new receptor families gain assessment support.
696
- */
697
- const UNIPROT_TO_SYMBOL = {
698
- P30988: "CALCR",
699
- P32241: "CALCRL",
700
- };
701
694
  /**
702
695
  * Partition a list of trials into primary (on-target or unresolved) and
703
696
  * related (off-target via a related family receptor). Each trial's
@@ -709,7 +702,6 @@ const UNIPROT_TO_SYMBOL = {
709
702
  export async function partitionTrialsByAttribution(trials, ctx) {
710
703
  const primary = [];
711
704
  const related = [];
712
- const relatedAccs = new Set();
713
705
  for (const trial of trials) {
714
706
  // Normalize interventions: ctgov bundle has string[], typed input may use objects.
715
707
  const interventions = Array.isArray(trial.interventions)
@@ -724,20 +716,12 @@ export async function partitionTrialsByAttribution(trials, ctx) {
724
716
  });
725
717
  if (result.match_confidence === "off_target") {
726
718
  related.push({ ...trial, match_confidence: "off_target" });
727
- for (const a of result.related_target_uniprots)
728
- relatedAccs.add(a);
729
719
  }
730
720
  else {
731
721
  primary.push({ ...trial, match_confidence: result.match_confidence });
732
722
  }
733
723
  }
734
- const related_receptor = relatedAccs.size > 0
735
- ? [...relatedAccs]
736
- .map((a) => UNIPROT_TO_SYMBOL[a] ?? a)
737
- .sort()
738
- .join("/")
739
- : undefined;
740
- return { primary, related, related_receptor };
724
+ return { primary, related };
741
725
  }
742
726
  /**
743
727
  * Classify why a candidate trial is considered relevant to the assessment.
@@ -804,10 +788,14 @@ export function classifyClinicalTrialConfidence(trial, geneSymbol, knownClassDru
804
788
  return "medium";
805
789
  return "low";
806
790
  }
807
- export async function assembleDiscoveryTrials(phase2, symbol, attrCtx, knownClassDrugNames) {
791
+ /** Rows the section carries; a candidate set beyond it is reported as truncated. */
792
+ const DISCOVERY_TRIAL_CAP = 100;
793
+ /** What emptied the discovery-trials section when nothing survived. */
794
+ const DISCOVERY_TRIALS_FILTER = "trial already carried by clinical_development.trials, attributed to a related family receptor, matched only by a generic condition string at low confidence, or attribution not eligible for toxicology aggregation";
795
+ export async function assembleDiscoveryTrials(phase2, symbol, attrCtx, knownClassDrugNames, attachAttribution) {
808
796
  const ctgov = phase2.phase1.collectors.ctgov;
809
797
  if (ctgov.coverage !== "available")
810
- return null;
798
+ return { coverage: "queried_no_data", error: { message: "ClinicalTrials.gov unavailable" } };
811
799
  const symU = symbol.toUpperCase();
812
800
  // Build a case-insensitive title-keyword regex from the target symbol and
813
801
  // common synonyms embedded in the assessment symbol name. For most targets
@@ -821,9 +809,7 @@ export async function assembleDiscoveryTrials(phase2, symbol, attrCtx, knownClas
821
809
  const inTitle = t.title.toUpperCase().includes(symU);
822
810
  return !(inInterventions || inTitle);
823
811
  });
824
- if (candidates.length === 0)
825
- return null;
826
- const partitioned = await partitionTrialsByAttribution(candidates, attrCtx);
812
+ const partitioned = candidates.length > 0 ? await partitionTrialsByAttribution(candidates, attrCtx) : { primary: [], related: [] };
827
813
  // Classify each primary candidate's relevance basis and drop low-confidence
828
814
  // condition-only matches — these are wrong-class contaminants (e.g., an
829
815
  // SGLT2 trial picked up because it studies T2D while assessing GLP1R).
@@ -836,7 +822,7 @@ export async function assembleDiscoveryTrials(phase2, symbol, attrCtx, knownClas
836
822
  phase: t.phase,
837
823
  status: t.status,
838
824
  conditions: t.conditions,
839
- // Convert null → undefined to match DiscoveryTrialRowV4Schema optional fields.
825
+ // Convert null → undefined to match DiscoveryTrialRowSchema optional fields.
840
826
  start_date: t.startDate ?? undefined,
841
827
  completion_date: t.primaryCompletionDate ?? undefined,
842
828
  match_confidence: t.match_confidence,
@@ -845,31 +831,29 @@ export async function assembleDiscoveryTrials(phase2, symbol, attrCtx, knownClas
845
831
  })
846
832
  .filter((row) => {
847
833
  // Drop low-confidence rows whose only basis is a generic condition string.
848
- // These fail the DiscoveryTrialRowV4Schema superRefine invariant and must
834
+ // These fail the DiscoveryTrialRowSchema superRefine invariant and must
849
835
  // not reach the dossier.
850
836
  return !(row.match_confidence === "low" && row.relevance_basis.kind === "condition_match");
851
837
  });
852
- const relatedRows = partitioned.related.map((t) => ({
853
- nct_id: t.nctId,
854
- title: t.title,
855
- phase: t.phase,
856
- status: t.status,
857
- conditions: t.conditions,
858
- start_date: t.startDate,
859
- completion_date: t.primaryCompletionDate,
860
- match_confidence: t.match_confidence,
861
- }));
862
- if (primaryRows.length === 0 && relatedRows.length === 0)
863
- return null;
864
- return {
865
- rows: primaryRows.slice(0, 100),
866
- ...(relatedRows.length > 0
867
- ? {
868
- related_target_trials: relatedRows,
869
- related_receptor: partitioned.related_receptor,
870
- }
871
- : {}),
872
- };
838
+ // Trials attributed to a related receptor rather than the assessed target
839
+ // are not discovery candidates at all, so they are not carried here.
840
+ const carried = primaryRows.slice(0, DISCOVERY_TRIAL_CAP);
841
+ const truncatedAway = primaryRows.length - carried.length;
842
+ const attributed = carried.map(attachAttribution);
843
+ const rows = attributed.filter((r) => r.eligible_for_toxicology_aggregation);
844
+ const section = coverageFromFilteredRows({
845
+ data: {
846
+ rows,
847
+ excluded_rows: attributed.filter((r) => !r.eligible_for_toxicology_aggregation),
848
+ },
849
+ retainedCount: rows.length,
850
+ // The cap is a carrying limit, not a judgement about a row, so it is
851
+ // reported as truncation rather than counted as a drop.
852
+ droppedCount: candidates.length - rows.length - truncatedAway,
853
+ filter: DISCOVERY_TRIALS_FILTER,
854
+ emptyReason: "every ClinicalTrials.gov record for the target is already carried by clinical_development.trials",
855
+ });
856
+ return truncatedAway > 0 && section.coverage === "available" ? { ...section, truncated: true } : section;
873
857
  }
874
858
  export function assembleEvidenceTimeline(phase2) {
875
859
  const pubmed = phase2.phase1.collectors.pubmedIndex;
@@ -2,13 +2,21 @@
2
2
  * Phase-4 deterministic assemblers.
3
3
  *
4
4
  * Each assembler maps Phase-1 / Phase-2 / Phase-3 outputs onto the
5
- * corresponding dossier section schema. Coverage is honored end-to-end:
6
- * when an upstream came back `queried_no_data` or the derived row set is
7
- * empty, the section is marked `queried_no_data` / `not_loaded` rather
8
- * than fabricated. Aggregate rows preserve their contributing evidence
9
- * under `evidence: [...]` arrays.
5
+ * corresponding dossier section schema. Coverage is honored end-to-end: an
6
+ * upstream that came back empty or was never consulted yields
7
+ * `queried_no_data` / `not_loaded` rather than a fabricated section.
8
+ *
9
+ * Where a section applies a threshold of its own, the decision runs through
10
+ * `coverageFromFilteredRows` over both counts — what survived and what the
11
+ * threshold discarded — so a section our own filter emptied reports
12
+ * `filtered` rather than an `available` section holding nothing. Deciding
13
+ * coverage first and filtering the payload afterwards is the shape that loses
14
+ * that distinction.
15
+ *
16
+ * Aggregate rows preserve their contributing evidence under `evidence: [...]`
17
+ * arrays.
10
18
  */
11
- import type { DossierV4Body, Entity, TractabilityV4Section } from "@inflexa-ai/harness/contracts/target-dossier.js";
19
+ import type { DossierBody, Entity, TractabilitySection } from "@inflexa-ai/harness/contracts/target-dossier.js";
12
20
  import type { Phase2Bundle } from "../steps/phase2-aggregate.js";
13
21
  import type { Phase3Bundle } from "../steps/phase3-aggregate.js";
14
22
  import type { ResolvedTarget } from "../schemas.js";
@@ -36,7 +44,7 @@ export declare function enrichTractabilityModalitiesWithApprovals<M extends {
36
44
  }>(modalities: M[], drugs: DrugForTractability[]): (M & {
37
45
  approved_drug_ids?: string[];
38
46
  })[];
39
- export declare function assembleTractability(resolved: ResolvedTarget, ot: OpenTargetsCoverageResult, drugs: DrugForTractability[]): TractabilityV4Section;
47
+ export declare function assembleTractability(resolved: ResolvedTarget, ot: OpenTargetsCoverageResult, drugs: DrugForTractability[]): TractabilitySection;
40
48
  /**
41
49
  * Build a body-only dossier from Phase-2 (and optionally Phase-3)
42
50
  * outputs. Synthesis-dependent fields stay `not_loaded` until Phase 5
@@ -44,7 +52,7 @@ export declare function assembleTractability(resolved: ResolvedTarget, ot: OpenT
44
52
  * attached here — phase5-persist computes it after synthesis is stamped
45
53
  * so the final derived values reflect liability bullets and safety flags.
46
54
  */
47
- export declare function assembleDossier(pool: Pool, phase2: Phase2Bundle, phase3?: Phase3Bundle, annotatorDeps?: ClinicalConsequenceAnnotatorDeps): Promise<DossierV4Body>;
55
+ export declare function assembleDossier(pool: Pool, phase2: Phase2Bundle, phase3?: Phase3Bundle, annotatorDeps?: ClinicalConsequenceAnnotatorDeps): Promise<DossierBody>;
48
56
  type ReconcileFaersClassPrecedent = {
49
57
  coverage?: "available" | "queried_no_data" | "not_loaded";
50
58
  data?: {
@@ -91,7 +99,7 @@ type ReconcileFaersOutput = {
91
99
  *
92
100
  * When target-level FAERS has data (coverage: "available"), pass through
93
101
  * unchanged. When class_precedent has FAERS-derived reports in per_organ,
94
- * fold them into a class-scoped FaersSummaryV4 payload with
102
+ * fold them into a class-scoped FAERS summary payload with
95
103
  * coverage: "available" and inference_path tagged — rather than leaving
96
104
  * the section as queried_no_data, which creates an evidence conflict
97
105
  * that the conflict-detector flags. When neither has data, return