@inflexa-ai/harness 0.35.8 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/auth/types.d.ts +5 -14
  2. package/dist/auth/types.js +3 -10
  3. package/dist/billing/usage-recorder.d.ts +1 -1
  4. package/dist/contracts/analogy-report.d.ts +1 -1
  5. package/dist/contracts/analogy-report.js +1 -1
  6. package/dist/contracts/chat-parts.d.ts +2 -3
  7. package/dist/contracts/index.d.ts +0 -4
  8. package/dist/contracts/index.js +0 -2
  9. package/dist/index.d.ts +0 -7
  10. package/dist/index.js +0 -21
  11. package/dist/lib/metrics.d.ts +1 -1
  12. package/dist/lib/otel-spans.d.ts +0 -2
  13. package/dist/lib/otel-spans.js +0 -2
  14. package/dist/loop/genai-spans.d.ts +14 -0
  15. package/dist/loop/genai-spans.js +35 -0
  16. package/dist/loop/run-agent.d.ts +1 -1
  17. package/dist/loop/run-agent.js +37 -4
  18. package/dist/runtime/assemble.d.ts +0 -3
  19. package/dist/runtime/assemble.js +0 -3
  20. package/dist/state/index.d.ts +0 -2
  21. package/dist/state/index.js +0 -1
  22. package/dist/state/init.js +1 -60
  23. package/dist/state/purge-analysis.d.ts +1 -2
  24. package/dist/state/purge-analysis.js +1 -2
  25. package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
  26. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
  27. package/dist/tools/bio/gene-preclinical-profile.js +1 -3
  28. package/dist/tools/lib/chembl-client.d.ts +1 -4
  29. package/dist/tools/lib/chembl-client.js +1 -4
  30. package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
  31. package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
  32. package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
  33. package/dist/tools/lib/clinical-trials-client.js +0 -3
  34. package/dist/tools/lib/identifier-resolver.d.ts +5 -7
  35. package/dist/tools/lib/identifier-resolver.js +5 -7
  36. package/dist/tools/lib/iuphar-client.d.ts +1 -3
  37. package/dist/tools/lib/iuphar-client.js +1 -3
  38. package/dist/tools/lib/opentargets-client.d.ts +1 -1
  39. package/dist/tools/lib/opentargets-client.js +1 -1
  40. package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
  41. package/dist/tools/lib/protein-family-normalize.js +5 -7
  42. package/dist/tools/lib/uniprot-client.d.ts +0 -7
  43. package/dist/tools/lib/uniprot-client.js +2 -9
  44. package/package.json +1 -1
  45. package/dist/contracts/target-assessment-row.d.ts +0 -85
  46. package/dist/contracts/target-assessment-row.js +0 -37
  47. package/dist/contracts/target-dossier.d.ts +0 -12423
  48. package/dist/contracts/target-dossier.js +0 -1282
  49. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
  50. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
  51. package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
  52. package/dist/prompts/target-assessment/briefs/index.js +0 -4
  53. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
  54. package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
  55. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
  56. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
  57. package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
  58. package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
  59. package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
  60. package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
  61. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
  62. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
  63. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
  64. package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
  65. package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
  66. package/dist/prompts/target-assessment/investigation/index.js +0 -3
  67. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
  68. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
  69. package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
  70. package/dist/prompts/target-assessment/modulator-triage.js +0 -31
  71. package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
  72. package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
  73. package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
  74. package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
  75. package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
  76. package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
  77. package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
  78. package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
  79. package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
  80. package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
  81. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
  82. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
  83. package/dist/state/target-assessments.d.ts +0 -68
  84. package/dist/state/target-assessments.js +0 -222
  85. package/dist/workflows/execute-target-assessment.d.ts +0 -118
  86. package/dist/workflows/execute-target-assessment.js +0 -631
  87. package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
  88. package/dist/workflows/target-assessment/assemblers/index.js +0 -4
  89. package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
  90. package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
  91. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
  92. package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
  93. package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
  94. package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
  95. package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
  96. package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
  97. package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
  98. package/dist/workflows/target-assessment/collectors/index.js +0 -538
  99. package/dist/workflows/target-assessment/coverage.d.ts +0 -97
  100. package/dist/workflows/target-assessment/coverage.js +0 -86
  101. package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
  102. package/dist/workflows/target-assessment/decisions/index.js +0 -127
  103. package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
  104. package/dist/workflows/target-assessment/fanout/index.js +0 -156
  105. package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
  106. package/dist/workflows/target-assessment/investigation/index.js +0 -419
  107. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
  108. package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
  109. package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
  110. package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
  111. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
  112. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
  113. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
  114. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
  115. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
  116. package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
  117. package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
  118. package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
  119. package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
  120. package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
  121. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
  122. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
  123. package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
  124. package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
  125. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
  126. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
  127. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
  128. package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
  129. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
  130. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
  131. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
  132. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
  133. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
  134. package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
  135. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
  136. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
  137. package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
  138. package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
  139. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
  140. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
  141. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
  142. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
  143. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
  144. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
  145. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
  146. package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
  147. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
  148. package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
  149. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
  150. package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
  151. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
  152. package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
  153. package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
  154. package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
  155. package/dist/workflows/target-assessment/metrics.d.ts +0 -16
  156. package/dist/workflows/target-assessment/metrics.js +0 -30
  157. package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
  158. package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
  159. package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
  160. package/dist/workflows/target-assessment/phase5-persist.js +0 -349
  161. package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
  162. package/dist/workflows/target-assessment/progress-stream.js +0 -110
  163. package/dist/workflows/target-assessment/progress.d.ts +0 -39
  164. package/dist/workflows/target-assessment/progress.js +0 -77
  165. package/dist/workflows/target-assessment/schemas.d.ts +0 -970
  166. package/dist/workflows/target-assessment/schemas.js +0 -318
  167. package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
  168. package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
  169. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
  170. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
  171. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
  172. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
  173. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
  174. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
  175. package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
  176. package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
  177. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
  178. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
  179. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
  180. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
  181. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
  182. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
  183. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
  184. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
  185. package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
  186. package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
  187. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
  188. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
  189. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
  190. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
  191. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
  192. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
  193. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
  194. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
  195. package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
  196. package/dist/workflows/target-assessment/synthesis/index.js +0 -354
  197. package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
  198. package/dist/workflows/target-assessment/synthesis-probes.js +0 -320
@@ -1,39 +0,0 @@
1
- /**
2
- * Phase 4 — dossier assembly.
3
- *
4
- * Calls `assembleDossier` (~3.5K LOC of aggregation over the Phase-3 bundle)
5
- * and returns the assembled `DossierBody`. The safety-corroboration and
6
- * claim-investigation sections are stamped by the workflow body between this
7
- * step and synthesis; the Phase-5 synthesis sections (translational commentary,
8
- * liability bullets, safety-flags trail) are stamped by the Phase-5 persist
9
- * step.
10
- *
11
- * The DBOS workflow body wraps this in `DBOS.runStep({name: "phase4-assemble"})`
12
- * so recovery on a fresh replica replays the cached output without re-running
13
- * the compute. Replay caching earns its keep here because `annotatorDeps`
14
- * carries the per-row clinical-consequence annotator's LLM seam: assembly makes
15
- * bounded model calls for off-target rows that have no cached annotation. Those
16
- * calls are per-row best-effort — a failure leaves the row's original fallback —
17
- * and their results are cached in Postgres, so a repeat assessment of the same
18
- * target reaches them without an LLM call at all.
19
- */
20
- import { DossierBodySchema } from "@inflexa-ai/harness/contracts/target-dossier.js";
21
- import { z } from "zod";
22
- import { assembleDossier } from "./assemblers/index.js";
23
- import { Phase3BundleSchema } from "./steps/phase3-aggregate.js";
24
- export const Phase4OutputSchema = z.object({
25
- assessmentId: z.string(),
26
- dossier: DossierBodySchema,
27
- });
28
- export { Phase3BundleSchema };
29
- /**
30
- * Run Phase 4 — assemble the dossier body from the Phase-3 bundle.
31
- * Safely cached on DBOS replay; the caller supplies the annotator seam.
32
- */
33
- export async function phase4Assemble(pool, phase3, annotatorDeps, logger) {
34
- const dossier = await assembleDossier(pool, phase3.phase2, phase3, annotatorDeps, logger);
35
- return {
36
- assessmentId: phase3.phase2.phase1.resolved.assessmentId,
37
- dossier,
38
- };
39
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Phase 5 — stamps the four Phase-5 synthesis outputs into the Phase-4 dossier,
3
- * runs the deterministic refinement passes (coverage-qualifier reconcile,
4
- * no-liabilities disclosure rewrite, recommendation citation validation +
5
- * demotion, recommendation quality gates), computes derived fields, and
6
- * validates against `DossierSchema`.
7
- *
8
- * The DB write (`setDossier` / `markFailed`) belongs to the workflow's terminal
9
- * handler; this module returns the validated dossier instead of writing it.
10
- * Typed throws (`DossierSchemaViolationError`, `DossierDerivedInvariantError`)
11
- * let that handler dispatch to `markFailed` with the matching `error.kind`.
12
- *
13
- * Every pass here is deterministic. The direction-of-effect audit
14
- * (`auditCitationDirections`) is not among them because it needs an LLM call,
15
- * and this module runs after synthesis has closed.
16
- */
17
- import type { Logger } from "../../lib/logger.js";
18
- import { type DossierBody } from "@inflexa-ai/harness/contracts/target-dossier.js";
19
- import type { OrganSignalProjection } from "./lib/fda-label-safety.js";
20
- import type { Phase2Bundle } from "./steps/phase2-aggregate.js";
21
- import type { DossierRecommendationStepOutput, LiabilityBulletsStepOutput, SafetyFlagsTrailStepOutput, TranslationalCommentaryStepOutput } from "./synthesis/index.js";
22
- export declare class DossierSchemaViolationError extends Error {
23
- readonly issues: readonly unknown[];
24
- constructor(message: string, issues: readonly unknown[]);
25
- }
26
- export declare class DossierDerivedInvariantError extends Error {
27
- readonly cause?: unknown | undefined;
28
- constructor(message: string, cause?: unknown | undefined);
29
- }
30
- export interface Phase5PersistInput {
31
- /** Operational logging seam; omitted falls back to no-op. */
32
- readonly logger?: Logger;
33
- readonly assessmentId: string;
34
- readonly phase4Dossier: DossierBody;
35
- readonly phase2: Phase2Bundle;
36
- /**
37
- * Per-organ signals segmented from the FDA labels the approval-precedent
38
- * step fetched. Null when no indication resolved and no label was queried.
39
- */
40
- readonly regulatoryOrganSignals: OrganSignalProjection | null;
41
- readonly synthesis: {
42
- readonly bullets: LiabilityBulletsStepOutput;
43
- readonly flags: SafetyFlagsTrailStepOutput;
44
- readonly commentary: TranslationalCommentaryStepOutput;
45
- readonly recommendation: DossierRecommendationStepOutput;
46
- };
47
- }
48
- export interface Phase5PersistResult {
49
- readonly assessmentId: string;
50
- readonly dossier: Record<string, unknown>;
51
- readonly bytes: number;
52
- }
53
- /**
54
- * Run Phase-5 persist as a pure function. Returns the validated dossier (and
55
- * computed bytes) on success. Throws `DossierDerivedInvariantError` when
56
- * `computeDerivedFields` rejects the dossier; throws
57
- * `DossierSchemaViolationError` when the schema rejects the final dossier.
58
- */
59
- export declare function phase5Persist(input: Phase5PersistInput): Promise<Phase5PersistResult>;
@@ -1,349 +0,0 @@
1
- /**
2
- * Phase 5 — stamps the four Phase-5 synthesis outputs into the Phase-4 dossier,
3
- * runs the deterministic refinement passes (coverage-qualifier reconcile,
4
- * no-liabilities disclosure rewrite, recommendation citation validation +
5
- * demotion, recommendation quality gates), computes derived fields, and
6
- * validates against `DossierSchema`.
7
- *
8
- * The DB write (`setDossier` / `markFailed`) belongs to the workflow's terminal
9
- * handler; this module returns the validated dossier instead of writing it.
10
- * Typed throws (`DossierSchemaViolationError`, `DossierDerivedInvariantError`)
11
- * let that handler dispatch to `markFailed` with the matching `error.kind`.
12
- *
13
- * Every pass here is deterministic. The direction-of-effect audit
14
- * (`auditCitationDirections`) is not among them because it needs an LLM call,
15
- * and this module runs after synthesis has closed.
16
- */
17
- import { createNoopLogger } from "../../lib/console-logger.js";
18
- import { DossierSchema, } from "@inflexa-ai/harness/contracts/target-dossier.js";
19
- import { assembleRegulatoryOrganSignals, deterministicTranslationalCommentary } from "./assemblers/index.js";
20
- import { validateRecommendationCitations } from "./lib/citation-validator.js";
21
- import { computeDerivedFields } from "./lib/compute-derived.js";
22
- import { detectStructuralEvidenceConflicts } from "./lib/evidence-conflict-detector.js";
23
- import { applyRecommendationQualityGates } from "./lib/recommendation-quality-gates.js";
24
- import { sanitizeRecommendation } from "./lib/recommendation-sanitizer.js";
25
- const SOFT_CAP_BYTES = 16 * 1024 * 1024;
26
- // ── Typed throws — handled in §6 terminal handler ────────────────────
27
- export class DossierSchemaViolationError extends Error {
28
- issues;
29
- constructor(message, issues) {
30
- super(message);
31
- this.issues = issues;
32
- this.name = "DossierSchemaViolationError";
33
- }
34
- }
35
- export class DossierDerivedInvariantError extends Error {
36
- cause;
37
- constructor(message, cause) {
38
- super(message);
39
- this.cause = cause;
40
- this.name = "DossierDerivedInvariantError";
41
- }
42
- }
43
- // ── Internal helpers ─────────────────────────────────────────────────
44
- function approximateSize(d) {
45
- return Buffer.byteLength(JSON.stringify(d), "utf-8");
46
- }
47
- function truncateOversize(d) {
48
- const next = structuredClone(d);
49
- const ta = next.reference_biology.therapeutic_area_associations;
50
- if (ta.coverage === "available" && ta.data.rows.length > 50) {
51
- ta.data.rows = ta.data.rows.slice(0, 50);
52
- ta.truncated = true;
53
- }
54
- const ind = next.indications;
55
- if (ind.coverage === "available" && ind.data.rows.length > 25) {
56
- ind.data.rows = ind.data.rows.slice(0, 25);
57
- ind.truncated = true;
58
- }
59
- const ppi = next.reference_biology.ppi_network;
60
- if (ppi.coverage === "available" && ppi.data.partners.length > 50) {
61
- ppi.data.partners = ppi.data.partners.slice(0, 50);
62
- ppi.truncated = true;
63
- }
64
- return next;
65
- }
66
- function pickCommentarySeverity(row) {
67
- const blob = `${row.predicate} ${row.commentary}`.toLowerCase();
68
- if (row.topic === "ko_phenotype" && /pre[- ]?weaning|embryonic|lethal/i.test(blob)) {
69
- return "caution";
70
- }
71
- if (row.topic === "expression_translation" && /(≥|>=|two[- ]?step|2[- ]?step|diverge|divergence|rank.*differ)/i.test(blob)) {
72
- return "caution";
73
- }
74
- if (/queried.+(no data|no calls|empty|no tissue)/i.test(blob)) {
75
- return "gap";
76
- }
77
- return "ok";
78
- }
79
- const CITED_PMID_RE = /\bPMID[:\s]?(\d{6,9})\b/gi;
80
- /**
81
- * A synthesised claim stands on the papers it cites. One that cites none says
82
- * so — a section path is a pointer into this dossier, not a record a reader
83
- * can independently check.
84
- */
85
- function supportFromCitedPmids(text, reasonWhenUncited) {
86
- const pmids = [...new Set([...text.matchAll(CITED_PMID_RE)].map((m) => m[1]))];
87
- if (pmids.length === 0)
88
- return { state: "unknown", reason: reasonWhenUncited };
89
- return { state: "scored", evidence: pmids.map((pmid) => ({ pmid, source: "pubmed" })) };
90
- }
91
- function stampSynthesis(logger, phase4Dossier, syn, phase2, regulatoryOrganSignals) {
92
- const next = structuredClone(phase4Dossier);
93
- next.safety_profile.regulatory_organ_signals = assembleRegulatoryOrganSignals(regulatoryOrganSignals);
94
- if (syn.bullets.coverage === "available") {
95
- next.liability_summary.liability_bullets = syn.bullets.data.bullets.map((b) => ({
96
- text: b.text,
97
- rationale: b.rationale,
98
- category: b.category,
99
- support: supportFromCitedPmids(`${b.text} ${b.rationale} ${b.evidence_pointer}`, `bullet cites ${b.evidence_pointer} rather than a paper`),
100
- }));
101
- }
102
- if (syn.flags.coverage === "available" && syn.flags.data.flags.length > 0) {
103
- next.safety_profile.target_organ_liabilities = {
104
- coverage: "available",
105
- data: {
106
- rows: syn.flags.data.flags.map((f) => ({
107
- organ: f.organ,
108
- severity: f.severity,
109
- trail: f.trail,
110
- mechanism_hypothesis: f.mechanism_hypothesis,
111
- support: supportFromCitedPmids(f.trail, "the audit trail is assembled from dossier sections and cites no paper"),
112
- })),
113
- },
114
- };
115
- }
116
- else if (syn.flags.coverage === "queried_no_data") {
117
- next.safety_profile.target_organ_liabilities = { coverage: "queried_no_data", error: syn.flags.error };
118
- }
119
- const deterministicAll = deterministicTranslationalCommentary(phase2);
120
- const deterministicSorted = [...deterministicAll].sort((a, b) => {
121
- const order = { caution: 0, gap: 1, ok: 2 };
122
- return order[a.severity] - order[b.severity];
123
- });
124
- const deterministic = deterministicSorted.slice(0, 4);
125
- const agentRows = syn.commentary.coverage === "available"
126
- ? syn.commentary.data.rows.slice(0, 4).map((r) => ({
127
- severity: pickCommentarySeverity(r),
128
- text: `${r.predicate}. ${r.commentary}`,
129
- }))
130
- : [];
131
- const allRows = [...deterministic, ...agentRows].slice(0, 8);
132
- if (allRows.length > 0) {
133
- next.reference_biology.preclinical.translational_commentary = {
134
- coverage: "available",
135
- data: { rows: allRows },
136
- };
137
- }
138
- else if (syn.commentary.coverage === "queried_no_data") {
139
- next.reference_biology.preclinical.translational_commentary = {
140
- coverage: "queried_no_data",
141
- error: syn.commentary.error,
142
- };
143
- }
144
- const recommendation = syn.recommendation.coverage === "available"
145
- ? {
146
- coverage: "available",
147
- data: sanitizeRecommendation(syn.recommendation.data),
148
- }
149
- : {
150
- coverage: "queried_no_data",
151
- error: syn.recommendation.error,
152
- };
153
- next.executive_recommendation = recommendation;
154
- if (syn.flags.coverage !== "available" && next.executive_recommendation.coverage === "available") {
155
- const qual = next.executive_recommendation.data.coverage_qualifier;
156
- if (qual) {
157
- if (!qual.sections_unavailable.includes("safety_profile.target_organ_liabilities")) {
158
- qual.sections_unavailable.push("safety_profile.target_organ_liabilities");
159
- }
160
- const errorKind = syn.flags.error?.kind ?? "unavailable";
161
- const annotation = `The deterministic safety-flag audit trail was not generated for this dossier (synthesis step \`safety-flags-trail\` failed with \`${errorKind}\`); the safety-bullet citations below are not anchored to a per-flag audit row.`;
162
- qual.note = qual.note ? `${annotation} ${qual.note}` : annotation;
163
- }
164
- }
165
- const diagnostics = [syn.bullets.diagnostic, syn.flags.diagnostic, syn.commentary.diagnostic, syn.recommendation.diagnostic];
166
- next.analytics.synthesis_diagnostics = {
167
- coverage: "available",
168
- data: { rows: diagnostics },
169
- };
170
- if (syn.recommendation.coverage === "available") {
171
- try {
172
- const audit = validateRecommendationCitations(syn.recommendation.data, next);
173
- if (audit.citations_unresolved.length > 0) {
174
- logger.warn("recommendation_audit: unresolved citation(s)", {
175
- unresolvedCount: audit.citations_unresolved.length,
176
- unresolved: audit.citations_unresolved,
177
- });
178
- }
179
- next.analytics.recommendation_audit = {
180
- coverage: "available",
181
- data: audit,
182
- };
183
- }
184
- catch (err) {
185
- next.analytics.recommendation_audit = {
186
- coverage: "queried_no_data",
187
- error: { message: err instanceof Error ? err.message : String(err) },
188
- };
189
- }
190
- }
191
- return next;
192
- }
193
- function applyCitationAuditDemotion(rec, audit) {
194
- const flagged = audit.citations_unresolved.filter((u) => u.surface === "organ_claim_without_probe_pass");
195
- if (flagged.length === 0)
196
- return rec;
197
- const flaggedExcerpts = flagged.map((f) => ("excerpt" in f ? f.excerpt : ""));
198
- const keptRisks = [];
199
- const demotedTexts = new Set();
200
- for (const risk of rec.key_risks) {
201
- const isFlagged = flaggedExcerpts.some((excerpt) => excerpt.length > 0 && risk.includes(excerpt.slice(0, Math.min(80, excerpt.length))));
202
- if (isFlagged) {
203
- demotedTexts.add(risk);
204
- }
205
- else {
206
- keptRisks.push(risk);
207
- }
208
- }
209
- const unverified = flagged.map((f) => ({
210
- text: [...demotedTexts].find((t) => "excerpt" in f && t.includes(f.excerpt.slice(0, Math.min(80, f.excerpt.length)))) ?? ("excerpt" in f ? f.excerpt : ""),
211
- bullet_category: f.bullet_category,
212
- surface: "organ_claim_without_probe_pass",
213
- }));
214
- return {
215
- ...rec,
216
- key_risks: keptRisks,
217
- coverage_qualifier: {
218
- ...rec.coverage_qualifier,
219
- unverified_bullets: unverified,
220
- },
221
- };
222
- }
223
- function rewriteNoLiabilitiesDisclosure(dossier) {
224
- const ls = dossier.liability_summary;
225
- if (!ls)
226
- return;
227
- const bullets = Array.isArray(ls.liability_bullets)
228
- ? ls.liability_bullets
229
- : [];
230
- const current = String(ls.no_liabilities_disclosure ?? "");
231
- if (current.startsWith("insufficient safety data"))
232
- return;
233
- const m = current.match(/(\d+) of 4 safety data sources returned data/);
234
- if (!m)
235
- return;
236
- const N = m[1];
237
- if (bullets.length === 0) {
238
- ls.no_liabilities_disclosure =
239
- `${N} of 4 safety data sources returned data; no class-level liabilities synthesised`;
240
- }
241
- else {
242
- ls.no_liabilities_disclosure =
243
- `${bullets.length} liabilities identified across ${N} of 4 safety data sources`;
244
- }
245
- }
246
- function resolveSection(dossier, path) {
247
- let cur = dossier;
248
- for (const part of path.split(".")) {
249
- if (cur == null || typeof cur !== "object")
250
- return { resolved: false };
251
- cur = cur[part];
252
- }
253
- if (cur === undefined)
254
- return { resolved: false };
255
- const coverage = cur && typeof cur === "object" && typeof cur.coverage === "string" ? cur.coverage : undefined;
256
- return { resolved: true, coverage };
257
- }
258
- function reconcileCoverageQualifier(dossier) {
259
- const cq = dossier.executive_recommendation
260
- ?.data?.coverage_qualifier;
261
- if (!cq || !Array.isArray(cq.sections_unavailable))
262
- return;
263
- cq.sections_unavailable = cq.sections_unavailable.filter((entry) => {
264
- const path = String(entry).split(" (")[0].trim();
265
- const { resolved, coverage } = resolveSection(dossier, path);
266
- if (!resolved)
267
- return false;
268
- return coverage !== "available";
269
- });
270
- }
271
- // ── Pure-function entry point ────────────────────────────────────────
272
- /**
273
- * Run Phase-5 persist as a pure function. Returns the validated dossier (and
274
- * computed bytes) on success. Throws `DossierDerivedInvariantError` when
275
- * `computeDerivedFields` rejects the dossier; throws
276
- * `DossierSchemaViolationError` when the schema rejects the final dossier.
277
- */
278
- export async function phase5Persist(input) {
279
- const logger = (input.logger ?? createNoopLogger()).named("phase5-persist").with({ assessmentId: input.assessmentId });
280
- // The dossier rides through this function as an untyped working blob; the
281
- // `as unknown as DossierBody` casts below let the typed helpers operate on
282
- // it. None of these casts is load-bearing for soundness:
283
- // `DossierSchema.safeParse(fullDossier)` at the end is the single
284
- // validation gate, and it throws before anything is returned.
285
- let dossier = stampSynthesis(logger, input.phase4Dossier, input.synthesis, input.phase2, input.regulatoryOrganSignals);
286
- // Demote organ-claim flagged bullets into coverage_qualifier.unverified_bullets.
287
- const exec = dossier.executive_recommendation;
288
- if (exec?.coverage === "available") {
289
- const audit = dossier.analytics.recommendation_audit?.data;
290
- if (audit && exec.data) {
291
- dossier.executive_recommendation.data = applyCitationAuditDemotion(exec.data, audit);
292
- }
293
- }
294
- reconcileCoverageQualifier(dossier);
295
- rewriteNoLiabilitiesDisclosure(dossier);
296
- const recommendationAudit = dossier.analytics.recommendation_audit?.data;
297
- const synthesisDiagnostics = dossier.analytics.synthesis_diagnostics?.coverage ===
298
- "available"
299
- ? dossier.analytics.synthesis_diagnostics.data.rows
300
- : [];
301
- const trials = dossier.clinical_development
302
- .trials;
303
- const hasDirectClinicalEvidence = trials?.coverage === "available" && (trials.data.rows ?? []).some((row) => row.eligible_for_toxicology_aggregation === true);
304
- const gated = applyRecommendationQualityGates({
305
- executive_recommendation: dossier.executive_recommendation,
306
- synthesis_diagnostics: synthesisDiagnostics,
307
- recommendation_audit: recommendationAudit,
308
- has_direct_clinical_evidence: hasDirectClinicalEvidence,
309
- });
310
- dossier.executive_recommendation = gated.executive_recommendation;
311
- const qg = dossier.analytics.quality_gates;
312
- dossier.analytics.quality_gates = {
313
- coverage: "available",
314
- data: {
315
- rows: [...(qg?.data?.rows ?? []), ...gated.quality_gates],
316
- },
317
- };
318
- let bytes = approximateSize(dossier);
319
- if (bytes > SOFT_CAP_BYTES) {
320
- dossier = truncateOversize(dossier);
321
- bytes = approximateSize(dossier);
322
- }
323
- let derived;
324
- try {
325
- derived = computeDerivedFields(dossier);
326
- }
327
- catch (err) {
328
- throw new DossierDerivedInvariantError(err instanceof Error ? err.message : String(err), err);
329
- }
330
- const fullDossier = { ...dossier, derived };
331
- const structuralConflicts = detectStructuralEvidenceConflicts(fullDossier);
332
- if (structuralConflicts.length > 0) {
333
- const existing = fullDossier.analytics.evidence_conflicts;
334
- const existingRows = existing?.coverage === "available" ? (existing.data?.rows ?? []) : [];
335
- fullDossier.analytics.evidence_conflicts = {
336
- coverage: "available",
337
- data: { rows: [...existingRows, ...structuralConflicts] },
338
- };
339
- }
340
- const parsed = DossierSchema.safeParse(fullDossier);
341
- if (!parsed.success) {
342
- throw new DossierSchemaViolationError("Dossier failed schema validation", parsed.error.issues);
343
- }
344
- return {
345
- assessmentId: input.assessmentId,
346
- dossier: parsed.data,
347
- bytes,
348
- };
349
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * TargetAssessmentProgressStream — the read side of a target assessment's
3
- * durable progress stream.
4
- *
5
- * `emitProgress` (the sibling `progress.ts`) writes one part per phase
6
- * transition and nothing reads it back, so an embedder that wants to show a
7
- * user how far an assessment has got had to reach for the durability engine
8
- * itself. This seam is that read, and it quarantines the engine the way
9
- * `RunLauncher` does for starting workflows: the delivered value is the
10
- * harness's own `TargetAssessmentProgressEvent` contract type, and no engine
11
- * type appears in the exported signature.
12
- *
13
- * It is far simpler than `execution/run-event-stream.ts`, and deliberately so.
14
- * A run fans out over a parent workflow and every sandbox-step child, each
15
- * writing its own stream, so that seam polls a ledger to discover children and
16
- * folds reconciling parts before delivery. A target assessment writes progress
17
- * from the parent body alone, under `workflowID === assessmentId`. One stream,
18
- * no discovery, no pool.
19
- *
20
- * It is a push subscription rather than a returned iterator because handing the
21
- * engine's async generator back to a caller would put the engine's model on the
22
- * very surface this seam exists to remove, and would leave nowhere to contain a
23
- * failure. A handler gives both, and matches the `EmitFn` idiom the writer side
24
- * already speaks.
25
- */
26
- import { type TargetAssessmentProgressEvent } from "@inflexa-ai/harness/contracts/target-dossier.js";
27
- import type { Logger } from "../../lib/logger.js";
28
- /**
29
- * Construction-time dependencies. The logger is where every contained failure
30
- * is reported, and defaults to silence so an embedder that wired none sees
31
- * nothing rather than having console forced on it.
32
- */
33
- export interface TargetAssessmentProgressStreamDeps {
34
- readonly logger?: Logger;
35
- }
36
- /**
37
- * Receives one progress event. May be async — the subscription awaits it, so a
38
- * handler is never invoked concurrently with itself. A throw is logged and
39
- * swallowed.
40
- */
41
- export type TargetAssessmentProgressHandler = (event: TargetAssessmentProgressEvent) => void | Promise<void>;
42
- /** Call-time parameters of one subscription. */
43
- export interface TargetAssessmentProgressSubscribeOptions {
44
- /** The assessment to observe. Also the workflow's id — TA sets the two equal. */
45
- readonly assessmentId: string;
46
- /** Where each delivered event goes. */
47
- readonly onEvent: TargetAssessmentProgressHandler;
48
- /** Aborting stops delivery and settles the returned promise. */
49
- readonly signal: AbortSignal;
50
- }
51
- /**
52
- * The progress read seam. One method, because an assessment-scoped subscription
53
- * is the whole capability — a general-purpose stream reader would leak the
54
- * engine's model back into the surface this exists to quarantine.
55
- */
56
- export interface TargetAssessmentProgressStream {
57
- /**
58
- * Deliver every progress event the assessment produces to `onEvent`,
59
- * resolving when the assessment's stream drains or when the signal aborts.
60
- *
61
- * The stream drains when the workflow stops being active. A workflow that
62
- * self-cancels on a 402 is no longer active, so the stream drains at the
63
- * `suspended` phase and this settles there — it does NOT wait for a later
64
- * `DBOS.resumeWorkflow`. Re-opening across a resume would mean polling the
65
- * row's status, which is the caller's business, not this seam's.
66
- *
67
- * Events arrive in the order the workflow wrote them, each exactly once.
68
- * There is no fold: the body emits each phase once and the engine caches a
69
- * step's stream-write offset, so a recovered workflow does not re-write one.
70
- * A fold would collapse nothing and cost a buffer.
71
- */
72
- subscribe(options: TargetAssessmentProgressSubscribeOptions): Promise<void>;
73
- }
74
- /** Build the DBOS-backed realization of {@link TargetAssessmentProgressStream}. */
75
- export declare function createTargetAssessmentProgressStream(deps?: TargetAssessmentProgressStreamDeps): TargetAssessmentProgressStream;
@@ -1,110 +0,0 @@
1
- /**
2
- * TargetAssessmentProgressStream — the read side of a target assessment's
3
- * durable progress stream.
4
- *
5
- * `emitProgress` (the sibling `progress.ts`) writes one part per phase
6
- * transition and nothing reads it back, so an embedder that wants to show a
7
- * user how far an assessment has got had to reach for the durability engine
8
- * itself. This seam is that read, and it quarantines the engine the way
9
- * `RunLauncher` does for starting workflows: the delivered value is the
10
- * harness's own `TargetAssessmentProgressEvent` contract type, and no engine
11
- * type appears in the exported signature.
12
- *
13
- * It is far simpler than `execution/run-event-stream.ts`, and deliberately so.
14
- * A run fans out over a parent workflow and every sandbox-step child, each
15
- * writing its own stream, so that seam polls a ledger to discover children and
16
- * folds reconciling parts before delivery. A target assessment writes progress
17
- * from the parent body alone, under `workflowID === assessmentId`. One stream,
18
- * no discovery, no pool.
19
- *
20
- * It is a push subscription rather than a returned iterator because handing the
21
- * engine's async generator back to a caller would put the engine's model on the
22
- * very surface this seam exists to remove, and would leave nowhere to contain a
23
- * failure. A handler gives both, and matches the `EmitFn` idiom the writer side
24
- * already speaks.
25
- */
26
- import { DBOS } from "@dbos-inc/dbos-sdk";
27
- import { TargetAssessmentProgressEventSchema } from "@inflexa-ai/harness/contracts/target-dossier.js";
28
- import { createNoopLogger } from "../../lib/console-logger.js";
29
- import { TA_PROGRESS_STREAM_KEY } from "./progress.js";
30
- /**
31
- * Shape `emitProgress` writes. The `data-target-assessment-progress` key is not
32
- * in the chat-part registry — it is the writer's private envelope — so the
33
- * payload, not the envelope, is what a caller gets.
34
- */
35
- function readEvent(value) {
36
- if (typeof value !== "object" || value === null)
37
- return null;
38
- const parsed = TargetAssessmentProgressEventSchema.safeParse(value.payload);
39
- return parsed.success ? parsed.data : null;
40
- }
41
- /** Build the DBOS-backed realization of {@link TargetAssessmentProgressStream}. */
42
- export function createTargetAssessmentProgressStream(deps = {}) {
43
- const baseLogger = (deps.logger ?? createNoopLogger()).named("ta-progress-stream");
44
- return {
45
- async subscribe({ assessmentId, onEvent, signal }) {
46
- const logger = baseLogger.with({ assessmentId });
47
- let resolveAbort;
48
- const aborted = new Promise((resolve) => {
49
- resolveAbort = resolve;
50
- });
51
- const generator = DBOS.readStream(assessmentId, TA_PROGRESS_STREAM_KEY);
52
- // The engine's reader exposes no cancellation: while it waits on a
53
- // quiet-but-active workflow it is suspended inside an await, where a
54
- // queued `return()` is only honoured once it next reaches a yield.
55
- // Teardown is therefore best-effort by construction, and the read's
56
- // own promise is raced against the signal rather than waiting on it.
57
- const onAbort = () => {
58
- resolveAbort();
59
- void generator.return(undefined).catch(() => {
60
- /* the read is already being abandoned */
61
- });
62
- };
63
- if (signal.aborted)
64
- onAbort();
65
- else
66
- signal.addEventListener("abort", onAbort, { once: true });
67
- const read = (async () => {
68
- try {
69
- for (;;) {
70
- const next = await generator.next();
71
- // Checked after the read, not before: `next()` is what
72
- // notices the workflow going inactive, and an aborted
73
- // subscription must deliver nothing further.
74
- if (next.done === true || signal.aborted)
75
- return;
76
- const event = readEvent(next.value);
77
- if (event === null) {
78
- logger.debug("dropped a stream value outside the progress contract");
79
- continue;
80
- }
81
- try {
82
- await onEvent(event);
83
- }
84
- catch (err) {
85
- logger.warn("progress handler threw", { phase: event.phase, ...logger.errorFields(err) });
86
- }
87
- }
88
- }
89
- catch (err) {
90
- // Observation is a diagnostic channel: `emitProgress` already
91
- // treats a failed write as best-effort so a lost frame cannot
92
- // fail the workflow. A read that propagated here would be
93
- // strictly less robust than the writer it observes.
94
- logger.error("progress stream read failed", logger.errorFields(err));
95
- }
96
- finally {
97
- void generator.return(undefined).catch(() => {
98
- /* already finished, or abandoned under abort */
99
- });
100
- }
101
- })();
102
- try {
103
- await Promise.race([read, aborted]);
104
- }
105
- finally {
106
- signal.removeEventListener("abort", onAbort);
107
- }
108
- },
109
- };
110
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Progress emission for the target-assessment DBOS workflow.
3
- *
4
- * Emits a typed `data-target-assessment-progress` part via
5
- * `DBOS.writeStream("progress", part)` and updates `cortex_target_assessments.progress`.
6
- * Both writes happen inside a `DBOS.runStep` so a recovered workflow does not
7
- * duplicate the event.
8
- *
9
- * Wire shape: `TargetAssessmentProgressEventSchema` in
10
- * `harness/src/contracts/target-dossier.ts`. The SSE route reads via
11
- * `DBOS.readStream(workflowId, "progress")` and folds-on-read by `phase`.
12
- */
13
- import type { Pool } from "pg";
14
- import type { TargetAssessmentPhase, TargetAssessmentProgressEvent } from "@inflexa-ai/harness/contracts/target-dossier.js";
15
- import type { Logger } from "../../lib/logger.js";
16
- /** DBOS stream key the SSE route reads via `DBOS.readStream(wfId, "progress")`. */
17
- export declare const TA_PROGRESS_STREAM_KEY = "progress";
18
- export type ProgressPhase = TargetAssessmentPhase;
19
- /**
20
- * Wire shape of one progress event. Matches the harness's contracts
21
- * `TargetAssessmentProgressEventSchema`. Wrapped in a typed chat part
22
- * envelope by the SSE route on the way out.
23
- */
24
- export interface ProgressPart {
25
- readonly type: "data-target-assessment-progress";
26
- readonly payload: TargetAssessmentProgressEvent;
27
- }
28
- export declare const PROGRESS_PERCENTS: Record<ProgressPhase, number>;
29
- export declare const PROGRESS_MESSAGES: Record<ProgressPhase, string>;
30
- /**
31
- * Emit one progress transition. MUST be called from inside the workflow
32
- * body — the `DBOS.runStep` wrapper makes it replay-safe (DBOS persists
33
- * the stream-write offset under `(workflowID, function_id)`; replay
34
- * returns cached without re-writing the part).
35
- *
36
- * Best-effort: a DB or stream failure logs and continues — progress
37
- * emission MUST NOT block the workflow.
38
- */
39
- export declare function emitProgress(pool: Pool, logger: Logger, assessmentId: string, phase: ProgressPhase): Promise<void>;