@inflexa-ai/harness 0.35.7 → 0.36.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.
- package/dist/auth/types.d.ts +5 -14
- package/dist/auth/types.js +3 -10
- package/dist/billing/usage-recorder.d.ts +1 -1
- package/dist/contracts/analogy-report.d.ts +1 -1
- package/dist/contracts/analogy-report.js +1 -1
- package/dist/contracts/chat-parts.d.ts +2 -3
- package/dist/contracts/index.d.ts +0 -4
- package/dist/contracts/index.js +0 -2
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -21
- package/dist/lib/metrics.d.ts +1 -1
- package/dist/lib/otel-spans.d.ts +0 -2
- package/dist/lib/otel-spans.js +0 -2
- package/dist/loop/genai-spans.d.ts +20 -0
- package/dist/loop/genai-spans.js +53 -0
- package/dist/loop/run-agent.d.ts +1 -1
- package/dist/loop/run-agent.js +9 -4
- package/dist/providers/ai-sdk.js +8 -0
- package/dist/runtime/assemble.d.ts +0 -3
- package/dist/runtime/assemble.js +0 -3
- package/dist/state/index.d.ts +0 -2
- package/dist/state/index.js +0 -1
- package/dist/state/init.js +1 -60
- package/dist/state/purge-analysis.d.ts +1 -2
- package/dist/state/purge-analysis.js +1 -2
- package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
- package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
- package/dist/tools/bio/gene-preclinical-profile.js +1 -3
- package/dist/tools/lib/chembl-client.d.ts +1 -4
- package/dist/tools/lib/chembl-client.js +1 -4
- package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
- package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
- package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
- package/dist/tools/lib/clinical-trials-client.js +0 -3
- package/dist/tools/lib/identifier-resolver.d.ts +5 -7
- package/dist/tools/lib/identifier-resolver.js +5 -7
- package/dist/tools/lib/iuphar-client.d.ts +1 -3
- package/dist/tools/lib/iuphar-client.js +1 -3
- package/dist/tools/lib/opentargets-client.d.ts +1 -1
- package/dist/tools/lib/opentargets-client.js +1 -1
- package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
- package/dist/tools/lib/protein-family-normalize.js +5 -7
- package/dist/tools/lib/uniprot-client.d.ts +0 -7
- package/dist/tools/lib/uniprot-client.js +2 -9
- package/package.json +9 -8
- package/dist/contracts/target-assessment-row.d.ts +0 -85
- package/dist/contracts/target-assessment-row.js +0 -37
- package/dist/contracts/target-dossier.d.ts +0 -12423
- package/dist/contracts/target-dossier.js +0 -1282
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
- package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
- package/dist/prompts/target-assessment/briefs/index.js +0 -4
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
- package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
- package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
- package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
- package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
- package/dist/prompts/target-assessment/investigation/index.js +0 -3
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
- package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
- package/dist/prompts/target-assessment/modulator-triage.js +0 -31
- package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
- package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
- package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
- package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
- package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
- package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
- package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
- package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
- package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
- package/dist/state/target-assessments.d.ts +0 -68
- package/dist/state/target-assessments.js +0 -222
- package/dist/workflows/execute-target-assessment.d.ts +0 -118
- package/dist/workflows/execute-target-assessment.js +0 -631
- package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
- package/dist/workflows/target-assessment/assemblers/index.js +0 -4
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
- package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
- package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
- package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
- package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
- package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
- package/dist/workflows/target-assessment/collectors/index.js +0 -538
- package/dist/workflows/target-assessment/coverage.d.ts +0 -97
- package/dist/workflows/target-assessment/coverage.js +0 -86
- package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
- package/dist/workflows/target-assessment/decisions/index.js +0 -127
- package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
- package/dist/workflows/target-assessment/fanout/index.js +0 -156
- package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
- package/dist/workflows/target-assessment/investigation/index.js +0 -419
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
- package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
- package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
- package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
- package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
- package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
- package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
- package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
- package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
- package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
- package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
- package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
- package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
- package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
- package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
- package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
- package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
- package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
- package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
- package/dist/workflows/target-assessment/metrics.d.ts +0 -16
- package/dist/workflows/target-assessment/metrics.js +0 -30
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
- package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
- package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
- package/dist/workflows/target-assessment/phase5-persist.js +0 -349
- package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
- package/dist/workflows/target-assessment/progress-stream.js +0 -110
- package/dist/workflows/target-assessment/progress.d.ts +0 -39
- package/dist/workflows/target-assessment/progress.js +0 -77
- package/dist/workflows/target-assessment/schemas.d.ts +0 -970
- package/dist/workflows/target-assessment/schemas.js +0 -318
- package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
- package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
- package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
- package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
- package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
- package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
- package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
- package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
- package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
- package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
- package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
- package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
- package/dist/workflows/target-assessment/synthesis/index.js +0 -354
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
- package/dist/workflows/target-assessment/synthesis-probes.js +0 -320
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `executeTargetAssessment` — the parent DBOS workflow.
|
|
3
|
-
*
|
|
4
|
-
* Shape (every external interaction is a `DBOS.runStep` so a crashed
|
|
5
|
-
* workflow replays from the cache instead of repeating side effects):
|
|
6
|
-
*
|
|
7
|
-
* Phase 0 `phase0Resolve` — only step allowed to throw
|
|
8
|
-
* (terminates → `target-unresolved`)
|
|
9
|
-
* Phase 1 15 collectors in parallel — coverage-envelope failures
|
|
10
|
-
* Progress `emitProgress("deciding")`
|
|
11
|
-
* Phase 2 2 decisions in parallel — LLM calls, attempt-numbered
|
|
12
|
-
* Progress `emitProgress("fanning_out")`
|
|
13
|
-
* Phase 3 4 fan-out blocks in parallel — per-item HTTP, withHost semaphore
|
|
14
|
-
* Progress `emitProgress("assembling")`
|
|
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
|
|
19
|
-
* Progress `emitProgress("synthesizing")`
|
|
20
|
-
* Phase 5a 3 per-section syntheses in parallel
|
|
21
|
-
* Phase 5b `dossierRecommendation` sequential
|
|
22
|
-
* Phase 5c `phase5Persist` (pure)
|
|
23
|
-
* Terminal outermost try/finally:
|
|
24
|
-
* - normal: setDossier → status="completed"
|
|
25
|
-
* - phase0 throw: markFailed → "target-unresolved"
|
|
26
|
-
* - schema violation: markFailed → "schema-violation"
|
|
27
|
-
* - other throw: markFailed → "unexpected-throw"
|
|
28
|
-
* - 402 self-cancel: markAssessmentSuspended → "suspended_insufficient_funds"
|
|
29
|
-
* - operator cancel: markFailed → "operator-cancelled"
|
|
30
|
-
* - soft-deleted row: no-op
|
|
31
|
-
*
|
|
32
|
-
* Workflow ID convention: `workflowID === assessmentId`. TA is one-shot
|
|
33
|
-
* (re-runs create new rows), so the workflowID equals the assessmentId
|
|
34
|
-
* directly. No `:runId` suffix.
|
|
35
|
-
*/
|
|
36
|
-
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
37
|
-
import { z } from "zod";
|
|
38
|
-
import { forSubAgent } from "../auth/types.js";
|
|
39
|
-
import { createNoopLogger } from "../lib/console-logger.js";
|
|
40
|
-
import { ATTR_INFLEXA_FANOUT_KEY, stableSpan } from "../lib/otel-spans.js";
|
|
41
|
-
import { unwrapOrThrow } from "../lib/result.js";
|
|
42
|
-
import { durableStep } from "../loop/run-step.js";
|
|
43
|
-
import { createPubMedTool } from "../tools/bio/pubmed.js";
|
|
44
|
-
import { resolveTarget } from "../tools/lib/identifier-resolver.js";
|
|
45
|
-
import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
|
|
46
|
-
import { getAssessment, markAssessmentSuspended, markFailed, setDossier } from "../state/target-assessments.js";
|
|
47
|
-
import { COLLECTOR_MANIFEST } from "./target-assessment/collectors/index.js";
|
|
48
|
-
import { drugsInClass, modulatorTriage } from "./target-assessment/decisions/index.js";
|
|
49
|
-
import { aesForOneClassDrug, aesForOneTrial, faersForOneModulator, polypharmForOneModulator, } from "./target-assessment/fanout/index.js";
|
|
50
|
-
import { fetchApprovalPrecedents, pickIndicationForPrecedents, precedentLabelSafety, renderApprovalPrecedents, } from "./target-assessment/lib/approval-precedents.js";
|
|
51
|
-
import { segmentLabelSafety } from "./target-assessment/lib/fda-label-safety.js";
|
|
52
|
-
import { readBudgetExceededMarker } from "./target-assessment/lib/llm-step.js";
|
|
53
|
-
import { assembleSafetyCorroboration } from "./target-assessment/lib/safety-corroboration.js";
|
|
54
|
-
import { investigateClaims, isClaimInvestigationBudgetExceeded } from "./target-assessment/investigation/index.js";
|
|
55
|
-
import { recordTerminalReason } from "./target-assessment/metrics.js";
|
|
56
|
-
import { elapsedSinceIso, recordRunCompleted } from "../lib/metrics.js";
|
|
57
|
-
import { phase4Assemble } from "./target-assessment/phase4-assemble.js";
|
|
58
|
-
import { DossierDerivedInvariantError, DossierSchemaViolationError, phase5Persist } from "./target-assessment/phase5-persist.js";
|
|
59
|
-
import { emitProgress } from "./target-assessment/progress.js";
|
|
60
|
-
import { dossierRecommendation, liabilityBullets, safetyFlagsTrail, translationalCommentary, } from "./target-assessment/synthesis/index.js";
|
|
61
|
-
// ── Workflow input ───────────────────────────────────────────────────
|
|
62
|
-
export const ExecuteTargetAssessmentInputSchema = z.object({
|
|
63
|
-
assessmentId: z.string(),
|
|
64
|
-
target: z.string(),
|
|
65
|
-
goal: z.string().nullable().optional(),
|
|
66
|
-
organizationId: z.string(),
|
|
67
|
-
requestedBy: z.string(),
|
|
68
|
-
billingContextId: z.string(),
|
|
69
|
-
});
|
|
70
|
-
// ── Helpers ──────────────────────────────────────────────────────────
|
|
71
|
-
/**
|
|
72
|
-
* Derive a sub-agent session from the durable `RunSession`. Identity, scope,
|
|
73
|
-
* and the opaque `auth` capability come from the parent session — no synthetic
|
|
74
|
-
* envelopes.
|
|
75
|
-
*/
|
|
76
|
-
function buildSession(input, agentId) {
|
|
77
|
-
return {
|
|
78
|
-
...input.runSession,
|
|
79
|
-
provenance: { agentId, callPath: [agentId] },
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Type guard: any phase result carrying `{kind: "budget-exceeded"}` means
|
|
84
|
-
* the LLM step wrapper has already self-cancelled the workflow. The body
|
|
85
|
-
* should propagate the sentinel up the call chain so no downstream
|
|
86
|
-
* coverage envelope is fabricated for it.
|
|
87
|
-
*/
|
|
88
|
-
function isBudgetCancel(value) {
|
|
89
|
-
return typeof value === "object" && value !== null && "kind" in value && value.kind === "budget-exceeded";
|
|
90
|
-
}
|
|
91
|
-
function wrapCollectorThrow(err) {
|
|
92
|
-
return {
|
|
93
|
-
coverage: "queried_no_data",
|
|
94
|
-
error: { message: err instanceof Error ? err.message : String(err) },
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Internal sentinel thrown by the workflow body when a 402 marker has
|
|
99
|
-
* been observed at a phase boundary — short-circuits the remaining
|
|
100
|
-
* phases and lands in the outermost catch, which sets `budgetExceeded`
|
|
101
|
-
* for the terminal dispatch.
|
|
102
|
-
*/
|
|
103
|
-
class BudgetExceededSkip extends Error {
|
|
104
|
-
constructor() {
|
|
105
|
-
super("budget exceeded — skipping remaining phases");
|
|
106
|
-
this.name = "BudgetExceededSkip";
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// ── Workflow registration ────────────────────────────────────────────
|
|
110
|
-
/**
|
|
111
|
-
* Register the executeTargetAssessment workflow with DBOS. Caller is
|
|
112
|
-
* responsible for setting `workflowID = assessmentId` via
|
|
113
|
-
* `DBOS.startWorkflow(_, { workflowID })`.
|
|
114
|
-
*/
|
|
115
|
-
export function registerExecuteTargetAssessment(deps) {
|
|
116
|
-
return DBOS.registerWorkflow(async (input) => {
|
|
117
|
-
return runExecuteTargetAssessmentBody(input, deps);
|
|
118
|
-
}, { name: "executeTargetAssessment" });
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Body extracted so tests can drive it without registering a workflow
|
|
122
|
-
* (the DBOS calls inside still rely on a workflow context being present).
|
|
123
|
-
*/
|
|
124
|
-
export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
125
|
-
const logger = (deps.logger ?? createNoopLogger()).with({ assessmentId: input.assessmentId });
|
|
126
|
-
// (§6.1) Outermost try/finally — every termination path dispatches the
|
|
127
|
-
// matching terminal handler. Phase 0 throws are caught here; every
|
|
128
|
-
// other phase wraps its body in coverage envelopes and does not throw.
|
|
129
|
-
// Budget-exceeded (billing-gateway 402) reaches here via `budgetExceeded` set
|
|
130
|
-
// when any LLM step returns the sentinel. The terminal block writes
|
|
131
|
-
// `status = "suspended_insufficient_funds"` via DBOS.runStep BEFORE
|
|
132
|
-
// materialising the cancel — the trailing `DBOS.cancelWorkflow` +
|
|
133
|
-
// `DBOS.runStep("self-cancel-budget-exceeded")` puts the workflow into
|
|
134
|
-
// CANCELLED status (the only state `DBOS.resumeWorkflow` replays).
|
|
135
|
-
// Ownership defaults to true for inputs persisted before the field existed
|
|
136
|
-
// (a workflow recovered across this deploy) — those were always Cortex-owned.
|
|
137
|
-
const authorization = {
|
|
138
|
-
runSession: input.runSession,
|
|
139
|
-
ownsMandate: input.ownsMandate ?? true, // oss-core-managed-ok
|
|
140
|
-
};
|
|
141
|
-
let phase0Error;
|
|
142
|
-
let schemaViolation;
|
|
143
|
-
let derivedViolation;
|
|
144
|
-
let unexpectedError;
|
|
145
|
-
let budgetExceeded = false;
|
|
146
|
-
let dossierForPersist;
|
|
147
|
-
let bytesForPersist = 0;
|
|
148
|
-
try {
|
|
149
|
-
// (§5.3, §6.3) Phase 0 — target resolution. The only legitimate throw
|
|
150
|
-
// site. On throw, the catch below records `phase0Error` and the
|
|
151
|
-
// terminal block dispatches `markFailed({kind: "target-unresolved"})`.
|
|
152
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "resolving");
|
|
153
|
-
const resolved = await DBOS.runStep(async () => {
|
|
154
|
-
const r = await resolveTarget(input.target, logger);
|
|
155
|
-
return {
|
|
156
|
-
assessmentId: input.assessmentId,
|
|
157
|
-
goal: input.goal ?? null,
|
|
158
|
-
...r,
|
|
159
|
-
};
|
|
160
|
-
}, { name: "ta-phase0-resolve" }).catch((err) => {
|
|
161
|
-
phase0Error = err;
|
|
162
|
-
throw err;
|
|
163
|
-
});
|
|
164
|
-
// (§5.4) Phase 1 — 15 collectors in parallel. Each is its own DBOS
|
|
165
|
-
// step; coverage failures cache as `queried_no_data` and replay
|
|
166
|
-
// returns the cached envelope. The per-collector run functions emit
|
|
167
|
-
// different bundle shapes; the union is widened to `unknown` here
|
|
168
|
-
// and `Phase1Bundle` is reconstructed by manifest key below.
|
|
169
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "collecting");
|
|
170
|
-
const manifest = COLLECTOR_MANIFEST;
|
|
171
|
-
const collectorCtx = { ncbiApiKey: deps.ncbiApiKey, logger };
|
|
172
|
-
const collectorPairs = await Promise.all(manifest.map((entry) => DBOS.runStep(() => entry.run(resolved, collectorCtx), {
|
|
173
|
-
name: `ta-collector:${entry.id}`,
|
|
174
|
-
})
|
|
175
|
-
.catch((err) => wrapCollectorThrow(err))
|
|
176
|
-
.then((result) => [entry.id, result])));
|
|
177
|
-
const collectorById = Object.fromEntries(collectorPairs);
|
|
178
|
-
const phase1 = {
|
|
179
|
-
resolved,
|
|
180
|
-
collectors: {
|
|
181
|
-
opentargets: collectorById["opentargets"],
|
|
182
|
-
chemblModulators: collectorById["chembl-modulators"],
|
|
183
|
-
ctgov: collectorById["ctgov"],
|
|
184
|
-
faersByTarget: collectorById["faers-by-target"],
|
|
185
|
-
expressionHuman: collectorById["expression-human"],
|
|
186
|
-
expressionMultiSpecies: collectorById["expression-multi-species"],
|
|
187
|
-
clinvar: collectorById["clinvar"],
|
|
188
|
-
cbioportal: collectorById["cbioportal"],
|
|
189
|
-
impc: collectorById["impc"],
|
|
190
|
-
monarch: collectorById["monarch"],
|
|
191
|
-
pubmedIndex: collectorById["pubmed-index"],
|
|
192
|
-
pathways: collectorById["pathways"],
|
|
193
|
-
stringPpi: collectorById["string-ppi"],
|
|
194
|
-
familyComplexes: collectorById["family-complexes"],
|
|
195
|
-
therapeuticPrograms: collectorById["therapeutic-programs"],
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
// (§5.5, §5.11) Phase 2 — decisions. Lazy billing resolution happens
|
|
199
|
-
// inside the chat provider's closure (TA-aware provider in §12). The
|
|
200
|
-
// session carries the `billingContextId` the provider reads from.
|
|
201
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "deciding");
|
|
202
|
-
const decisionSession = buildSession(input, "ta-decisions");
|
|
203
|
-
const [modulatorRes, drugsRes] = await Promise.all([
|
|
204
|
-
modulatorTriage(phase1, {
|
|
205
|
-
chatProvider: deps.chatProvider,
|
|
206
|
-
session: forSubAgent(decisionSession, "modulator-triage"),
|
|
207
|
-
model: deps.decisionModel,
|
|
208
|
-
attempt: 0,
|
|
209
|
-
}),
|
|
210
|
-
drugsInClass(phase1, {
|
|
211
|
-
chatProvider: deps.chatProvider,
|
|
212
|
-
session: forSubAgent(decisionSession, "drugs-in-class"),
|
|
213
|
-
model: deps.decisionModel,
|
|
214
|
-
attempt: 0,
|
|
215
|
-
}),
|
|
216
|
-
]);
|
|
217
|
-
if (isBudgetCancel(modulatorRes) || isBudgetCancel(drugsRes)) {
|
|
218
|
-
// An LLM step already sent the budget-exceeded marker. Skip the
|
|
219
|
-
// remaining phases and fall through to the terminal block, which
|
|
220
|
-
// dispatches `markAssessmentSuspended` and materialises the cancel.
|
|
221
|
-
budgetExceeded = true;
|
|
222
|
-
throw new BudgetExceededSkip();
|
|
223
|
-
}
|
|
224
|
-
const phase2 = {
|
|
225
|
-
phase1,
|
|
226
|
-
decisions: {
|
|
227
|
-
modulatorTriage: modulatorRes,
|
|
228
|
-
failedTrialClassifier: {
|
|
229
|
-
coverage: "not_loaded",
|
|
230
|
-
reason: "deterministic in Phase 4 (classifyFailureReason)",
|
|
231
|
-
},
|
|
232
|
-
offTargetCurator: {
|
|
233
|
-
coverage: "not_loaded",
|
|
234
|
-
reason: "deterministic in Phase 4 (aggregateOffTargetPanel)",
|
|
235
|
-
},
|
|
236
|
-
drugsInClass: drugsRes,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
// (§5.6) Phase 3 — fan-out blocks. Each block iterates items via
|
|
240
|
-
// Promise.all over DBOS.runStep; `withHost` semaphores inside each
|
|
241
|
-
// item function cap concurrency to 4 per host.
|
|
242
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "fanning_out");
|
|
243
|
-
const triage = phase2.decisions.modulatorTriage;
|
|
244
|
-
const triageShortlist = triage.coverage === "available"
|
|
245
|
-
? triage.data.shortlist.map((m) => ({
|
|
246
|
-
moleculeChemblId: m.moleculeChemblId,
|
|
247
|
-
preferredName: m.preferredName,
|
|
248
|
-
maxPhase: m.maxPhase,
|
|
249
|
-
firstApproval: m.firstApproval,
|
|
250
|
-
rationale: m.rationale ?? "",
|
|
251
|
-
}))
|
|
252
|
-
: [];
|
|
253
|
-
const chembl = phase1.collectors.chemblModulators;
|
|
254
|
-
const primaryTargetChemblId = chembl.coverage === "available" ? chembl.data.targetChemblId : null;
|
|
255
|
-
const polypharmItems = triageShortlist.map((m) => ({
|
|
256
|
-
moleculeChemblId: m.moleculeChemblId,
|
|
257
|
-
preferredName: m.preferredName,
|
|
258
|
-
primaryTargetChemblId,
|
|
259
|
-
}));
|
|
260
|
-
const ctgov = phase1.collectors.ctgov;
|
|
261
|
-
const trialItems = [];
|
|
262
|
-
if (ctgov.coverage === "available") {
|
|
263
|
-
const seen = new Set();
|
|
264
|
-
for (const t of [...ctgov.data.failed, ...ctgov.data.active]) {
|
|
265
|
-
if (!t.nctId || seen.has(t.nctId))
|
|
266
|
-
continue;
|
|
267
|
-
seen.add(t.nctId);
|
|
268
|
-
trialItems.push({ nctId: t.nctId, title: t.title });
|
|
269
|
-
if (trialItems.length >= 30)
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const dic = phase2.decisions.drugsInClass;
|
|
274
|
-
const classDrugItems = dic.coverage === "available"
|
|
275
|
-
? dic.data.drugs.map((d) => ({
|
|
276
|
-
moleculeChemblId: d.moleculeChemblId,
|
|
277
|
-
preferredName: d.preferredName,
|
|
278
|
-
maxPhase: d.maxPhase,
|
|
279
|
-
firstApproval: d.firstApproval,
|
|
280
|
-
}))
|
|
281
|
-
: [];
|
|
282
|
-
async function runFanoutBlock(items, stepKindForName, keyOf, fn) {
|
|
283
|
-
return Promise.all(items.map((item) => {
|
|
284
|
-
const key = keyOf(item);
|
|
285
|
-
const stepName = `ta-fanout:${stepKindForName}:${key}`;
|
|
286
|
-
return DBOS.runStep(() => {
|
|
287
|
-
stableSpan(stepName, `ta-fanout:${stepKindForName}`, { [ATTR_INFLEXA_FANOUT_KEY]: key });
|
|
288
|
-
return fn(item);
|
|
289
|
-
}, { name: stepName }).catch((err) => wrapCollectorThrow(err));
|
|
290
|
-
}));
|
|
291
|
-
}
|
|
292
|
-
const [perModulatorFaersResults, perTrialAesResults, perModulatorPolypharmResults, perClassDrugAesResults] = await Promise.all([
|
|
293
|
-
runFanoutBlock(triageShortlist, "modulator-faers", (item) => item.moleculeChemblId, faersForOneModulator),
|
|
294
|
-
runFanoutBlock(trialItems, "trial-aes", (item) => item.nctId, aesForOneTrial),
|
|
295
|
-
runFanoutBlock(polypharmItems, "modulator-polypharm", (item) => item.moleculeChemblId, (item) => polypharmForOneModulator(item, logger)),
|
|
296
|
-
runFanoutBlock(classDrugItems, "class-drug-aes", (item) => item.moleculeChemblId, aesForOneClassDrug),
|
|
297
|
-
]);
|
|
298
|
-
const phase3 = {
|
|
299
|
-
phase2,
|
|
300
|
-
fanout: {
|
|
301
|
-
perModulatorFaers: { results: perModulatorFaersResults },
|
|
302
|
-
perTrialAes: { results: perTrialAesResults },
|
|
303
|
-
perModulatorPolypharm: { results: perModulatorPolypharmResults },
|
|
304
|
-
perClassDrugAes: { results: perClassDrugAesResults },
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
// (§5.7) Phase 4 — assembly. Single DBOS step. The annotator seam lets
|
|
308
|
-
// the off-target panel's clinical-consequence pass reach its LLM on a
|
|
309
|
-
// cache miss; per-row failures leave the row's fallback in place.
|
|
310
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "assembling");
|
|
311
|
-
const annotatorDeps = {
|
|
312
|
-
provider: deps.chatProvider,
|
|
313
|
-
session: buildSession(input, "off-target-annotator"),
|
|
314
|
-
model: deps.decisionModel,
|
|
315
|
-
...(deps.usageRecorder ? { usageRecorder: deps.usageRecorder } : {}),
|
|
316
|
-
};
|
|
317
|
-
const phase4 = await DBOS.runStep(() => phase4Assemble(deps.pool, phase3, annotatorDeps, logger), {
|
|
318
|
-
name: "ta-phase4-assemble",
|
|
319
|
-
});
|
|
320
|
-
// (§5.8-pre) Approval-precedent grounding — one deterministic openFDA
|
|
321
|
-
// lookup feeding two projections of the same typed labels: the markdown
|
|
322
|
-
// block every synthesis prompt receives, and the per-organ regulatory
|
|
323
|
-
// signals the dossier carries. A precedent-lookup failure must NOT fail
|
|
324
|
-
// the whole assessment: a fetch throw degrades to a "no precedents" block.
|
|
325
|
-
const precedentGrounding = await DBOS.runStep(async () => {
|
|
326
|
-
const indication = pickIndicationForPrecedents(phase4.dossier);
|
|
327
|
-
let result = null;
|
|
328
|
-
if (indication !== null) {
|
|
329
|
-
try {
|
|
330
|
-
result = await fetchApprovalPrecedents({ indication });
|
|
331
|
-
}
|
|
332
|
-
catch (err) {
|
|
333
|
-
logger.named("ta-approval-precedents").warn("openFDA lookup failed", { indication, ...logger.errorFields(err) });
|
|
334
|
-
result = null;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
block: renderApprovalPrecedents(indication, result),
|
|
339
|
-
// A resolved indication was queried even when the lookup
|
|
340
|
-
// degraded to nothing; only an unresolved one was not.
|
|
341
|
-
organSignals: indication === null ? null : segmentLabelSafety(precedentLabelSafety(result?.precedents ?? [])),
|
|
342
|
-
};
|
|
343
|
-
}, { name: "ta-approval-precedents" });
|
|
344
|
-
const approvalPrecedents = precedentGrounding.block;
|
|
345
|
-
// (§5.8-pre) The corroboration fold. It reads the label signals segmented
|
|
346
|
-
// above, so it runs here rather than at assembly — and here rather than at
|
|
347
|
-
// persist, because the claim investigation below is its consumer. The fold
|
|
348
|
-
// is pure over two cached step outputs, so it needs no step of its own: a
|
|
349
|
-
// replay recomputes the same record without a durable write.
|
|
350
|
-
const corroboration = assembleSafetyCorroboration({
|
|
351
|
-
phase1,
|
|
352
|
-
regulatoryOrganSignals: precedentGrounding.organSignals,
|
|
353
|
-
});
|
|
354
|
-
// (§5.8-pre) The claim investigation — propose a mechanism, argue
|
|
355
|
-
// against it, re-verify, converge. Runs before synthesis so the
|
|
356
|
-
// synthesis prompts read verdicts rather than bare agreement.
|
|
357
|
-
const investigation = await investigateClaims({ corroboration, dossier: phase4.dossier }, {
|
|
358
|
-
logger,
|
|
359
|
-
chatProvider: deps.chatProvider,
|
|
360
|
-
session: buildSession(input, "ta-claim-investigation"),
|
|
361
|
-
model: deps.synthesisModel,
|
|
362
|
-
attempt: 0,
|
|
363
|
-
runStep: durableStep,
|
|
364
|
-
critiqueTools: [createPubMedTool({ ...(deps.ncbiApiKey ? { ncbiApiKey: deps.ncbiApiKey } : {}) })],
|
|
365
|
-
...(deps.claimInvestigation ? { config: deps.claimInvestigation } : {}),
|
|
366
|
-
...(deps.usageRecorder ? { usageRecorder: deps.usageRecorder } : {}),
|
|
367
|
-
});
|
|
368
|
-
if (isClaimInvestigationBudgetExceeded(investigation)) {
|
|
369
|
-
budgetExceeded = true;
|
|
370
|
-
throw new BudgetExceededSkip();
|
|
371
|
-
}
|
|
372
|
-
const phase4Investigated = {
|
|
373
|
-
...phase4,
|
|
374
|
-
dossier: {
|
|
375
|
-
...phase4.dossier,
|
|
376
|
-
safety_corroboration: corroboration,
|
|
377
|
-
claim_investigation: investigation,
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
// (§5.8) Phase 5 — three per-section syntheses in parallel.
|
|
381
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "synthesizing");
|
|
382
|
-
const synthesisDeps = (agentId) => ({
|
|
383
|
-
chatProvider: deps.chatProvider,
|
|
384
|
-
session: { ...buildSession(input, agentId) },
|
|
385
|
-
model: deps.synthesisModel,
|
|
386
|
-
attempt: 0,
|
|
387
|
-
approvalPrecedents,
|
|
388
|
-
});
|
|
389
|
-
const [bulletsRes, flagsRes, commentaryRes] = await Promise.all([
|
|
390
|
-
liabilityBullets(phase4Investigated, synthesisDeps("liability-bullets")),
|
|
391
|
-
safetyFlagsTrail(phase4Investigated, synthesisDeps("safety-flags-trail")),
|
|
392
|
-
translationalCommentary(phase4Investigated, synthesisDeps("translational-commentary")),
|
|
393
|
-
]);
|
|
394
|
-
if (isBudgetCancel(bulletsRes) || isBudgetCancel(flagsRes) || isBudgetCancel(commentaryRes)) {
|
|
395
|
-
budgetExceeded = true;
|
|
396
|
-
throw new BudgetExceededSkip();
|
|
397
|
-
}
|
|
398
|
-
// (§5.9) Phase 5b — sequential dossier-recommendation step.
|
|
399
|
-
const recommendationRes = await dossierRecommendation({
|
|
400
|
-
phase4: phase4Investigated,
|
|
401
|
-
perSection: {
|
|
402
|
-
liabilityBullets: bulletsRes,
|
|
403
|
-
safetyFlagsTrail: flagsRes,
|
|
404
|
-
translationalCommentary: commentaryRes,
|
|
405
|
-
},
|
|
406
|
-
}, synthesisDeps("dossier-recommendation"));
|
|
407
|
-
if (isBudgetCancel(recommendationRes)) {
|
|
408
|
-
budgetExceeded = true;
|
|
409
|
-
throw new BudgetExceededSkip();
|
|
410
|
-
}
|
|
411
|
-
// (§5.10) Phase 5c — pure persist function. The DB write moves to
|
|
412
|
-
// the terminal handler below; this step validates the v5 dossier
|
|
413
|
-
// and returns the persisted-shape bundle. Schema/derived violations
|
|
414
|
-
// throw and the terminal handler dispatches to `markFailed`.
|
|
415
|
-
const persisted = await DBOS.runStep(() => phase5Persist({
|
|
416
|
-
logger,
|
|
417
|
-
assessmentId: input.assessmentId,
|
|
418
|
-
phase4Dossier: phase4Investigated.dossier,
|
|
419
|
-
phase2,
|
|
420
|
-
regulatoryOrganSignals: precedentGrounding.organSignals,
|
|
421
|
-
synthesis: {
|
|
422
|
-
bullets: bulletsRes,
|
|
423
|
-
flags: flagsRes,
|
|
424
|
-
commentary: commentaryRes,
|
|
425
|
-
recommendation: recommendationRes,
|
|
426
|
-
},
|
|
427
|
-
}), { name: "ta-phase5-persist" });
|
|
428
|
-
dossierForPersist = persisted.dossier;
|
|
429
|
-
bytesForPersist = persisted.bytes;
|
|
430
|
-
}
|
|
431
|
-
catch (err) {
|
|
432
|
-
if (err instanceof BudgetExceededSkip) {
|
|
433
|
-
// Already classified; the terminal block reads `budgetExceeded`.
|
|
434
|
-
}
|
|
435
|
-
else if (err instanceof DossierSchemaViolationError) {
|
|
436
|
-
schemaViolation = err;
|
|
437
|
-
}
|
|
438
|
-
else if (err instanceof DossierDerivedInvariantError) {
|
|
439
|
-
derivedViolation = err;
|
|
440
|
-
}
|
|
441
|
-
else if (phase0Error === undefined) {
|
|
442
|
-
unexpectedError = err;
|
|
443
|
-
}
|
|
444
|
-
// phase0Error is already set if it came from the Phase-0 catch above.
|
|
445
|
-
}
|
|
446
|
-
// (§6.1, §6.8) Terminal block. Read the row first; if soft-deleted,
|
|
447
|
-
// no-op (the user's delete during the run wins).
|
|
448
|
-
//
|
|
449
|
-
// Each terminal path records its `cortex.run.completed` outcome INSIDE the
|
|
450
|
-
// DBOS step that persists it, so a recovery replay reads the cached step
|
|
451
|
-
// and records nothing again. The duration runs from the row's `created_at`.
|
|
452
|
-
// The deleted path writes no row, so its record rides the read step itself.
|
|
453
|
-
const row = await DBOS.runStep(async () => {
|
|
454
|
-
const read = unwrapOrThrow(await getAssessment(deps.pool, input.assessmentId, input.organizationId));
|
|
455
|
-
if (read?.status === "deleted") {
|
|
456
|
-
recordRunCompleted({ workflow: "target_assessment", status: "canceled", durationMs: elapsedSinceIso(read.createdAt) });
|
|
457
|
-
}
|
|
458
|
-
return read;
|
|
459
|
-
}, { name: "ta-terminal-read-row" });
|
|
460
|
-
const recordTerminal = (status) => recordRunCompleted({ workflow: "target_assessment", status, durationMs: elapsedSinceIso(row?.createdAt) });
|
|
461
|
-
if (row?.status === "deleted") {
|
|
462
|
-
recordTerminalReason("deleted");
|
|
463
|
-
return {
|
|
464
|
-
assessmentId: input.assessmentId,
|
|
465
|
-
status: "deleted",
|
|
466
|
-
bytes: 0,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
// (§6.2) Normal completion — happy path wrote `dossierForPersist`.
|
|
470
|
-
if (!phase0Error && !schemaViolation && !derivedViolation && !unexpectedError && !budgetExceeded && dossierForPersist) {
|
|
471
|
-
try {
|
|
472
|
-
await DBOS.runStep(async () => {
|
|
473
|
-
unwrapOrThrow(await setDossier(deps.pool, input.assessmentId, dossierForPersist));
|
|
474
|
-
recordTerminal("completed");
|
|
475
|
-
}, { name: "ta-terminal-completed" });
|
|
476
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "completed");
|
|
477
|
-
}
|
|
478
|
-
catch (err) {
|
|
479
|
-
logger.named("ta-terminal").error("setDossier failed", logger.errorFields(err));
|
|
480
|
-
}
|
|
481
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-completed");
|
|
482
|
-
recordTerminalReason("completed");
|
|
483
|
-
return {
|
|
484
|
-
assessmentId: input.assessmentId,
|
|
485
|
-
status: "completed",
|
|
486
|
-
bytes: bytesForPersist,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
// (§6.3) Phase 0 throw — target unresolved.
|
|
490
|
-
if (phase0Error) {
|
|
491
|
-
await DBOS.runStep(async () => {
|
|
492
|
-
unwrapOrThrow(await markFailed(deps.pool, input.assessmentId, {
|
|
493
|
-
kind: "target-unresolved",
|
|
494
|
-
message: phase0Error instanceof Error ? phase0Error.message : String(phase0Error),
|
|
495
|
-
}));
|
|
496
|
-
recordTerminal("failed");
|
|
497
|
-
}, { name: "ta-terminal-failed-resolve" });
|
|
498
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
499
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
500
|
-
recordTerminalReason("target-unresolved");
|
|
501
|
-
return {
|
|
502
|
-
assessmentId: input.assessmentId,
|
|
503
|
-
status: "failed",
|
|
504
|
-
bytes: 0,
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
// (§6.4) Schema violation.
|
|
508
|
-
if (schemaViolation) {
|
|
509
|
-
await DBOS.runStep(async () => {
|
|
510
|
-
unwrapOrThrow(await markFailed(deps.pool, input.assessmentId, {
|
|
511
|
-
kind: "schema-invariant-violation",
|
|
512
|
-
message: schemaViolation.message,
|
|
513
|
-
details: schemaViolation.issues,
|
|
514
|
-
}));
|
|
515
|
-
recordTerminal("failed");
|
|
516
|
-
}, { name: "ta-terminal-failed-schema" });
|
|
517
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
518
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
519
|
-
recordTerminalReason("schema-violation");
|
|
520
|
-
return {
|
|
521
|
-
assessmentId: input.assessmentId,
|
|
522
|
-
status: "failed",
|
|
523
|
-
bytes: bytesForPersist,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
// (§6.4 — derived invariant variant — same terminal class, different kind.)
|
|
527
|
-
if (derivedViolation) {
|
|
528
|
-
await DBOS.runStep(async () => {
|
|
529
|
-
unwrapOrThrow(await markFailed(deps.pool, input.assessmentId, {
|
|
530
|
-
kind: "derived-invariant-violation",
|
|
531
|
-
message: derivedViolation.message,
|
|
532
|
-
}));
|
|
533
|
-
recordTerminal("failed");
|
|
534
|
-
}, { name: "ta-terminal-failed-derived" });
|
|
535
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
536
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
537
|
-
recordTerminalReason("derived-invariant-violation");
|
|
538
|
-
return {
|
|
539
|
-
assessmentId: input.assessmentId,
|
|
540
|
-
status: "failed",
|
|
541
|
-
bytes: bytesForPersist,
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
// (§6.6) Budget exceeded — drain the marker for telemetry, flip the
|
|
545
|
-
// row to `suspended_insufficient_funds`, then materialise the cancel.
|
|
546
|
-
// The trailing `DBOS.cancelWorkflow` + `DBOS.runStep` puts the
|
|
547
|
-
// workflow into CANCELLED status (the only state `DBOS.resumeWorkflow`
|
|
548
|
-
// will replay; SUCCESS / ERROR are terminal).
|
|
549
|
-
if (budgetExceeded) {
|
|
550
|
-
await DBOS.runStep(() => readBudgetExceededMarker(), {
|
|
551
|
-
name: "ta-terminal-drain-marker",
|
|
552
|
-
}).catch(() => null);
|
|
553
|
-
await DBOS.runStep(async () => {
|
|
554
|
-
unwrapOrThrow(await markAssessmentSuspended(deps.pool, input.assessmentId));
|
|
555
|
-
// A budget pause records as `canceled`, the same status the analysis run ledger gives it.
|
|
556
|
-
recordTerminal("canceled");
|
|
557
|
-
}, { name: "ta-terminal-suspended" });
|
|
558
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "suspended");
|
|
559
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-canceled");
|
|
560
|
-
const workflowId = DBOS.workflowID;
|
|
561
|
-
if (workflowId) {
|
|
562
|
-
await DBOS.cancelWorkflow(workflowId);
|
|
563
|
-
// Force one more durable hop so the cancel materialises before the
|
|
564
|
-
// body returns — without this trailing runStep the workflow would
|
|
565
|
-
// land SUCCESS in some DBOS versions because the body completed
|
|
566
|
-
// without re-entering the SDK after the cancel call.
|
|
567
|
-
await DBOS.runStep(async () => undefined, {
|
|
568
|
-
name: "ta-self-cancel-budget-exceeded",
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
recordTerminalReason("suspended-on-402");
|
|
572
|
-
return {
|
|
573
|
-
assessmentId: input.assessmentId,
|
|
574
|
-
status: "suspended_insufficient_funds",
|
|
575
|
-
bytes: 0,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
// (§6.5) Unexpected throw — bug somewhere, coverage envelope failed.
|
|
579
|
-
if (unexpectedError) {
|
|
580
|
-
await DBOS.runStep(async () => {
|
|
581
|
-
unwrapOrThrow(await markFailed(deps.pool, input.assessmentId, {
|
|
582
|
-
kind: "unexpected-throw",
|
|
583
|
-
message: unexpectedError instanceof Error ? unexpectedError.message : String(unexpectedError),
|
|
584
|
-
}));
|
|
585
|
-
recordTerminal("failed");
|
|
586
|
-
}, { name: "ta-terminal-failed-unexpected" });
|
|
587
|
-
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
588
|
-
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
589
|
-
recordTerminalReason("unexpected-throw");
|
|
590
|
-
return {
|
|
591
|
-
assessmentId: input.assessmentId,
|
|
592
|
-
status: "failed",
|
|
593
|
-
bytes: 0,
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
// Defensive: this branch is unreachable in practice (one of the
|
|
597
|
-
// categories above must have fired). Fall through to a generic failed
|
|
598
|
-
// status so a buggy refactor surfaces visibly.
|
|
599
|
-
await DBOS.runStep(async () => {
|
|
600
|
-
unwrapOrThrow(await markFailed(deps.pool, input.assessmentId, {
|
|
601
|
-
kind: "unexpected-throw",
|
|
602
|
-
message: "terminal block reached without classified outcome",
|
|
603
|
-
}));
|
|
604
|
-
recordTerminal("failed");
|
|
605
|
-
}, { name: "ta-terminal-failed-uncategorized" });
|
|
606
|
-
recordTerminalReason("unexpected-throw");
|
|
607
|
-
return {
|
|
608
|
-
assessmentId: input.assessmentId,
|
|
609
|
-
status: "failed",
|
|
610
|
-
bytes: 0,
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Best-effort revoke wrapped in a durableStep for replay idempotency. This
|
|
615
|
-
* wrapper swallows any runStep-level error so a terminal path is never
|
|
616
|
-
* aborted by a transient revoke failure.
|
|
617
|
-
*/
|
|
618
|
-
async function revokeRunAuthorizationSafe(deps, authorization, reason) {
|
|
619
|
-
const logger = (deps.logger ?? createNoopLogger()).named("execute-target-assessment");
|
|
620
|
-
try {
|
|
621
|
-
await DBOS.runStep(() => deps.runAuthorizer.revoke(authorization, reason), {
|
|
622
|
-
name: `ta-revoke-run-auth:${reason}`,
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
catch (err) {
|
|
626
|
-
logger.warn("revokeRunAuthorization failed", { reason, ...logger.errorFields(err) });
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
// Re-export the canonical TA input schema (consumers and the managed root
|
|
630
|
-
// validate against this shape on the trigger route).
|
|
631
|
-
export { TargetAssessmentInputSchema };
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { assembleEntity, assembleTractability, enrichTractabilityModalitiesWithApprovals, type DrugForTractability, reconcileFaersCoverage, assembleDossier, } from "./orchestrator.js";
|
|
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, assembleRegulatoryOrganSignals, buildOrganRollup, } from "./safety.js";
|
|
4
|
-
export { deterministicTranslationalCommentary, isClinicalMeasurement, isSelfReference } from "./literature.js";
|
|
5
|
-
export { aggregateTrialOutcomes } from "./trials.js";
|
|
@@ -1,4 +0,0 @@
|
|
|
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, assembleRegulatoryOrganSignals, buildOrganRollup, } from "./safety.js";
|
|
3
|
-
export { deterministicTranslationalCommentary, isClinicalMeasurement, isSelfReference } from "./literature.js";
|
|
4
|
-
export { aggregateTrialOutcomes } from "./trials.js";
|