From 2492710691a9106c591cfee52aa69966b0446879 Mon Sep 17 00:00:00 2001 From: Vince Alessi Date: Sun, 16 Aug 2026 05:46:59 -0700 Subject: [PATCH 1/3] Render server-native scientific results --- README.md | 21 + app/API-CONTRACT.md | 46 +- app/index.html | 5 +- app/js/app.js | 738 +++++++++++++++++- app/js/backend-http.js | 13 + app/js/snapshot-contract.js | 101 +++ app/styles.css | 42 + tests/backend-http.test.mjs | 29 + .../orchestrator-scientific-snapshot.json | 297 +++++++ tests/frontend-live-contract.test.mjs | 190 +++++ tests/helpers/load-functional-app.mjs | 27 +- verify_functional_app.mjs | 26 + 12 files changed, 1494 insertions(+), 41 deletions(-) create mode 100644 tests/backend-http.test.mjs create mode 100644 tests/fixtures/orchestrator-scientific-snapshot.json diff --git a/README.md b/README.md index 2c3ab1a..31c462f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ labels (REPRESENTATIVE / MODELED / NOT decision-grade) impossible to miss. # Integrated judging path (run from labrador-demo-orchestrator after bootstrap) uv run python app.py serve # UI + API on one process, port 8787 open http://127.0.0.1:8787/ +open "http://127.0.0.1:8787/?mode=scientific" # explicit v3 IRAK4 replay +open "http://127.0.0.1:8787/?run=LR-..." # attach to an existing run # Split-process frontend development bun serve.ts 4173 # serve app/ (loopback, port 4173) @@ -57,6 +59,13 @@ The app in `app/` runs against one of two data sources: Any backend that implements `POST /api/runs` and `GET /api/runs/:id/snapshot` gets the full three-screen UI for free. +The default URL retains the v0 demo-compatible setup. `?mode=scientific` is an +explicit opt-in that posts the checked-in IRAK4/RA `labrador.run-setup.v3` +replay frame with `presentationMode: SCIENTIFIC`, one HypGen run per selected +focus, and no representative overlay. `?run=` creates nothing; it +attaches read-only to that run and renders whichever snapshot contract the +server returns. + ## Honesty rules the UI enforces - **Ceilings, not quotas.** Setup numbers are caps. Stages report `requested` @@ -73,6 +82,18 @@ gets the full three-screen UI for free. `display_metric_basis=REPRESENTATIVE_DEMO_SCENARIO_V1`. Those values drive graph placement and the client-side Pareto view while native metrics and station artifacts remain unchanged and inspectable through run artifacts. +- **Scientific packets stay scientific.** A + `labrador.scientific-snapshot.v1` never uses browser or representative values + to determine comparison membership. The UI posts the terminal packets to + server Highlander, then renders its frontier, incomparable records, exact + hashes, and producer-grounded next evidence action. It does not substitute a + client frontier if that server result is absent. +- **Native branch artifacts remain native.** Scientific inspectors show exact + input/output refs and hashes, producer identity, origin, terminal reason and + message, and the unchanged payload—including clinical `simulated_*` names. +- **Representative watermark is opt-in.** It appears only when the scientific + snapshot explicitly reports both `presentation_mode: REPRESENTATIVE_DEMO` + and `representative_demo: true`. - **Three-axis plan map.** Highlander maps every returned plan as a numbered, selectable point using exactly P50 rNPV (ROI), recruitability, and simulation / tractability. Plausibility remains visible comparison context but does not diff --git a/app/API-CONTRACT.md b/app/API-CONTRACT.md index dddda67..165d6b5 100644 --- a/app/API-CONTRACT.md +++ b/app/API-CONTRACT.md @@ -265,9 +265,49 @@ Abstain (`abstention: true` + reason in `answer`) when the run can't support an answer. If the endpoint is missing, the client abstains on the backend's behalf — it never invents an answer. +## Scientific branch snapshot v1 + +The explicit `/?mode=scientific` path posts `labrador.run-setup.v3` and consumes +`labrador.scientific-snapshot.v1`. The default v0 path above remains supported. +The scientific response contains top-level `branches`; each branch has a real +`focus` and `nodes` keyed by `hypothesis_generator`, `clinical_simulation`, +`simulation`, and `roi_calculator`. Every public node exposes its terminal +`status`, exact `reason_code` and `message`, `output_origin`, input/output refs +and hashes, `producer`, and unchanged `artifact`. + +Scientific mode has three additional invariants: + +- `scientific_packet_excludes_representative_values` is `true`; presentation + values never determine hashes or comparison membership. +- A watermark is rendered only when both `presentation_mode` is + `REPRESENTATIVE_DEMO` and `representative_demo` is `true`. +- The browser displays `highlander.result` as returned by the server, including + `frontier`, `dominated`, `incomparable`, candidate objective observations, + and `nextEvidenceAction`. Until that result exists, the UI says it is waiting + and computes no substitute frontier. + +### `POST /api/runs/:id/highlander` + +Request: + +```json +{ "acknowledgeGaps": true } +``` + +The response may be the scientific snapshot directly or the orchestrator UI +state containing it at `scientific`. The frontend requires the refreshed +`labrador.scientific-snapshot.v1` and will not silently fall back to its legacy +client comparator. + +### Attach mode + +`/?run=` performs no create call. It attaches read-only to the supplied +run and begins polling `GET /api/runs/:id/snapshot`. It can be combined with +`mode=scientific` to label the intent before the first snapshot arrives. + ## Not in v0 (documented gaps) -- **Highlander runs client-side** off the snapshot's programs (same Pareto +- **Legacy v0 Highlander runs client-side** off the snapshot's programs (same Pareto logic in mock and http modes). Its baseline vector is exactly `metrics.rnpv` (ROI), `metrics.recruit` (recruitability), and the simulation / tractability value (`metrics.tractability_fit`, or native `metrics.support` @@ -278,8 +318,8 @@ answer. If the endpoint is missing, the client abstains on the backend's behalf remaining comparison-panel height beneath the objective table. The RA demo's Z value is explicitly labeled representative branch-context fit; the shared native cached dossier remains attached and is not presented as a - candidate-specific simulation result. A server-side Highlander job endpoint - is a future addition. + candidate-specific simulation result. Scientific v1 uses the server endpoint + documented above instead. - **Review actions** (shortlist/constraint/exclude) are recorded in a client-side audit log only; `POST /api/runs/:id/actions` is future work. - **No auth/identity.** Do not present actor fields as verified. diff --git a/app/index.html b/app/index.html index 42202ef..ae64cc2 100644 --- a/app/index.html +++ b/app/index.html @@ -10,6 +10,7 @@ +
@@ -274,11 +275,13 @@

Tradeoffs remain visible.

+ +

Raw objective comparison

Original units, uncertainty, qualifiers, and three-axis frontier membership. No global winner.

illustrative
ProgramP50 rNPVPositive rNPVRecruitabilityEnrollPlausibilitySimulation / tractabilityBaseline status
-
Three-dimensional Pareto view: P50 rNPV × recruitability × simulation / tractability. Each numbered point is one plan. Plans with identical vectors fan slightly around their shared coordinate. Nominal frontier projection The line is a projected guide through complete non-dominated records, not a frontier surface or decision threshold. Missing values remain on a separate shelf. The current RA demo uses a labeled representative tractability fit on the Z axis; its native cached dossier remains shared across plans.
+
Three-dimensional Pareto view: P50 rNPV × recruitability × simulation / tractability. Each numbered point is one plan. Plans with identical vectors fan slightly around their shared coordinate. Nominal frontier projection The line is a projected guide through complete non-dominated records, not a frontier surface or decision threshold. Missing values remain on a separate shelf. The current RA demo uses a labeled representative tractability fit on the Z axis; its native cached dossier remains shared across plans.
diff --git a/app/js/app.js b/app/js/app.js index c91ab0a..92a1f3d 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -1,8 +1,15 @@ import { createHttpBackend } from "./backend-http.js"; import { interpretabilityView, + isScientificSnapshot, normalizeStageTruth, resolveBackendBase, + scientificCandidateId, + scientificComparisonStatus, + scientificHighlanderCandidate, + scientificModuleId, + scientificNodeForStage, + scientificStageId, stationPayloadFor } from "./snapshot-contract.js"; @@ -13,7 +20,15 @@ var params = new URLSearchParams(window.location.search); var mode = params.get("backend") === "mock" ? "mock" : "http"; var base = resolveBackendBase(window.location.search, window.location.origin); - return { mode: mode, base: base, http: mode === "http" ? createHttpBackend(base) : null }; + var launchMode = params.get("mode") === "scientific" ? "scientific" : "legacy"; + var runId = params.get("run"); + return { + mode: mode, + base: base, + launchMode: launchMode, + runId: runId && runId.trim() ? runId.trim() : null, + http: mode === "http" ? createHttpBackend(base) : null + }; }()); (function () { @@ -159,6 +174,14 @@ lastUpdated: null, highlanderReady: false, highlanderLaunched: false, + highlanderLaunching: false, + highlanderResult: null, + highlanderResultHash: null, + scientificSnapshot: false, + representativeDemo: false, + scientificPacketExcludesRepresentativeValues: false, + presentationMode: null, + executionMode: null, selectedProgramId: null, scenario: "balanced", metrics: { @@ -205,7 +228,9 @@ programDetail: document.getElementById("program-detail"), scenarioMeta: document.getElementById("scenario-meta"), weightList: document.getElementById("weight-list"), - chatLog: document.getElementById("chat-log") + chatLog: document.getElementById("chat-log"), + representativeWatermark: document.getElementById("representative-watermark"), + serverHighlanderResult: document.getElementById("server-highlander-result") }; function escapeHTML(value) { @@ -291,8 +316,12 @@ var valid = indicationValid && biomarkers.valid && papers.valid && hypotheses.valid; if (biomarkers.valid && hypotheses.valid) { - var branches = biomarkers.value * hypotheses.value; - elements.branchPreview.textContent = "Up to " + biomarkers.value + " biomarker" + (biomarkers.value === 1 ? "" : "s") + " and up to " + branches + " hypothesis branch" + (branches === 1 ? "" : "es") + "."; + if (BOOT.launchMode === "scientific") { + elements.branchPreview.textContent = "Up to " + biomarkers.value + " evidence focus branch" + (biomarkers.value === 1 ? "" : "es") + "; one full HypGen run per focus. Scientific packets exclude presentation values."; + } else { + var branches = biomarkers.value * hypotheses.value; + elements.branchPreview.textContent = "Up to " + biomarkers.value + " biomarker" + (biomarkers.value === 1 ? "" : "s") + " and up to " + branches + " hypothesis branch" + (branches === 1 ? "" : "es") + "."; + } } else { elements.branchPreview.textContent = "Enter valid ceilings to preview requested capacity."; } @@ -306,6 +335,89 @@ }; } + function buildScientificSetup(validation) { + return { + schemaVersion: "labrador.run-setup.v3", + execution: { + mode: "REPLAY", + presentationMode: "SCIENTIFIC" + }, + exploration: { + evidenceRequest: { + ask: "new_question", + target: "Can IRAK4 inhibition suppress synovial inflammation in " + validation.indication + "?", + depth: "deep" + }, + focus: { maxBranches: validation.biomarkers }, + hypothesis: { + profile: "default", + roi: { + requestId: "IRAK4-RA-scientific-roi", + comparables: [], + execution: { + simulations: 128, + seed: 42, + simulationAssumptions: {} + } + } + } + }, + program: { + frame: { + schemaVersion: "labrador.scientific-program-frame.v1", + frameId: "IRAK4-RA-scientific", + basis: "ANALYST_SUPPLIED", + asset: { + name: "IRAK4 inhibitor", + modality: "small_molecule", + sponsor: null + }, + target: { + symbol: "IRAK4", + direction: "inhibit", + uniprotAccession: "Q9NWZ3" + }, + disease: { name: validation.indication, subtype: null }, + biomarkerDefaults: { + prevalenceInDisease: 0.4, + assayAvailable: true + }, + endpoint: { + name: "ACR50 response", + type: "binary", + expectedEffectSize: 0.3 + }, + tissue: "synovium", + simulationContext: { + interactionToDisrupt: "IRAK4 catalytic function", + mechanismHypothesis: "orthosteric", + asOfDate: null + }, + notes: [ + "Scientific assembly may use only this frame plus branch evidence and hypothesis output." + ] + }, + valuationFrame: { + base_year: 2026, + valuation_year: 2026, + launch_year: 2034, + filing_year: 2026, + currency: "USD", + geography: "United States", + therapeutic_area: "Immunology", + target_population: "Adults with active rheumatoid arthritis", + line_of_therapy: "Second line", + route: "ORAL", + current_stage: "Preclinical", + modality: "SMALL_MOLECULE", + target: "IRAK4", + expansion_launch_year: null, + notes: "Analyst-supplied valuation assumptions; separate from scientific thesis fields." + } + } + }; + } + function updateDualRange(name, changed) { var low = document.getElementById(name + "-low"); var high = document.getElementById(name + "-high"); @@ -616,6 +728,34 @@ node.metadata.warnings = Array.isArray(stageRow.warnings) ? stageRow.warnings.slice() : []; } + function applyScientificNodeTruth(node, stage, program) { + if (!state.scientificSnapshot || !program || stage === "biomarker") return; + var source = scientificNodeForStage(program.scientificBranch, stage); + if (!source) return; + var producer = source.producer && typeof source.producer === "object" + ? source.producer + : {}; + var producerIdentity = [producer.repository, producer.git_sha].filter(Boolean).join(" @ "); + node.execution = source.status === "CANNOT_COMPLETE" ? "FAILED" : (source.status || "UNREPORTED"); + node.outputOrigin = source.output_origin || "UNREPORTED"; + node.resultBasis = source.status === "COMPLETE" ? "NATIVE PRODUCER ARTIFACT" : "MISSING"; + node.runtime = producerIdentity || source.module_id || scientificModuleId(stage); + node.reason = source.reason_code || null; + node.metadata.presentationStatus = source.status || "UNREPORTED"; + node.metadata.outputOrigin = source.output_origin || "UNREPORTED"; + node.metadata.reasonCode = source.reason_code || null; + node.metadata.terminalMessage = source.message || null; + node.metadata.inputRef = source.input_ref || null; + node.metadata.inputHash = source.input_hash || null; + node.metadata.outputRef = source.output_ref || null; + node.metadata.outputHash = source.output_hash || null; + node.metadata.durationMs = source.duration_ms; + node.metadata.exitCode = source.exit_code; + node.metadata.producer = producer; + node.metadata.qualifiers = []; + node.metadata.warnings = source.message ? [source.message] : []; + } + function bindStage(stage) { var httpMode = BOOT.mode === "http"; state.nodes.forEach(function (node) { @@ -713,6 +853,7 @@ node.metadata.displayMetricNote = program.displayMetricNote || null; } applyBackendStageTruth(node, stage); + applyScientificNodeTruth(node, stage, program); // When a real station produced this record, its verbatim output rides along. // The payload never overwrites execution status or a failure/skip reason; // provenance labels upgrade only for records that actually resolved. @@ -1057,7 +1198,9 @@ var interaction = state.previewId === node.id ? "SELECTED + PREVIEWED" : "SELECTED"; var payloadSection = ""; if (node.metadata.stationPayload) { - payloadSection = httpMode + payloadSection = state.scientificSnapshot + ? '
Native producer artifact

Exact producer field names and values are shown below. This includes native simulated_* clinical fields; the browser does not rename or rewrite the stored artifact.

' + escapeHTML(JSON.stringify(node.metadata.stationPayload, null, 2)) + "
" + : httpMode ? '
Native station artifact

The complete module payload is retained unchanged and bound to this run by hash. Technical field names are omitted from the judging presentation; the readable interpretation above is display-only.

' : '
Station output (verbatim)

Key names are the station’s own honesty contract; nothing is renamed for display, and score is not a probability of approval.

' + escapeHTML(JSON.stringify(node.metadata.stationPayload, null, 2)) + "
"; } @@ -1076,8 +1219,24 @@ if (node.metadata.displayMetricBasis === "REPRESENTATIVE_DEMO_SCENARIO_V1") { liveDefinition = (definition ? definition.label + " · " + definition.unit + ". " : "") + "Representative branch value for demo comparison; not a native module output. The native artifact remains attached unchanged."; } - var interpretability = node.metadata.stationPayload && node.metadata.stationPayload.interpretability; + var interpretability = node.metadata.stationPayload && ( + node.metadata.stationPayload.interpretability || + (node.metadata.stationPayload.cards && node.metadata.stationPayload.cards.interpretability) + ); var readableInterpretability = renderInterpretability(interpretability); + var scientificTruth = state.scientificSnapshot + ? '
Producer terminal status' + escapeHTML(node.metadata.presentationStatus || "UNREPORTED") + "
" + : ""; + var scientificAudit = state.scientificSnapshot + ? '
Artifact lineage & hashes
    ' + + "
  • Input ref: " + escapeHTML(node.metadata.inputRef || "not supplied") + "
  • " + + "
  • Input hash: " + escapeHTML(node.metadata.inputHash || "not supplied") + "
  • " + + "
  • Output ref: " + escapeHTML(node.metadata.outputRef || "not supplied") + "
  • " + + "
  • Output hash: " + escapeHTML(node.metadata.outputHash || "not supplied") + "
  • " + + "
  • Producer: " + escapeHTML(JSON.stringify(node.metadata.producer || {})) + "
  • " + + "
  • Duration / exit: " + escapeHTML(node.metadata.durationMs === null || node.metadata.durationMs === undefined ? "not supplied" : node.metadata.durationMs + " ms") + " / " + escapeHTML(node.metadata.exitCode === null || node.metadata.exitCode === undefined ? "not supplied" : node.metadata.exitCode) + "
  • " + + "
" + : ""; elements.inspectorHeading.textContent = node.label; elements.inspectorSubtitle.textContent = node.stage + " · " + node.id; @@ -1091,11 +1250,13 @@ '
UI freshness' + escapeHTML(state.freshness.toUpperCase()) + "
" + '
Stage result' + escapeHTML(node.metadata.presentationStatus || node.execution || "UNREPORTED") + "
" + '
Display metric basis' + escapeHTML(String(node.metadata.displayMetricBasis || "NATIVE_DERIVED").replace(/_/g, " ")) + "
" + + scientificTruth + "
" + '
Active display value' + escapeHTML(value) + '

' + escapeHTML(liveDefinition) + " Uncertainty: " + escapeHTML(judgeFacingText(node.uncertainty || "not supplied")) + ".

" + readableInterpretability + - '
Run qualifications

Reason: ' + escapeHTML(judgeFacingText(node.reason || "No stage reason code reported.")) + '

Qualifiers: ' + escapeHTML(judgeFacingText(qualifiers.length ? qualifiers.join(" · ") : "none reported")) + '

Warnings: ' + escapeHTML(judgeFacingText(visibleWarnings.length ? visibleWarnings.join(" · ") : "none reported")) + "

" + - '
Lineage & audit
  • Parent: ' + escapeHTML(parent ? parent.label : "none · root") + "
  • Direct descendants: " + childCount + "
  • Output origin: " + escapeHTML(origin) + "
  • Timestamp: " + escapeHTML(state.lastUpdated || "awaiting update") + "
  • Hash: " + escapeHTML(program ? program.hash : "unreported") + "
" + + '
Run qualifications

Reason code: ' + escapeHTML(node.reason || "No stage reason code reported.") + '

Terminal message: ' + escapeHTML(node.metadata.terminalMessage || "No terminal message reported.") + '

Qualifiers: ' + escapeHTML(judgeFacingText(qualifiers.length ? qualifiers.join(" · ") : "none reported")) + '

Warnings: ' + escapeHTML(judgeFacingText(visibleWarnings.length ? visibleWarnings.join(" · ") : "none reported")) + "

" + + scientificAudit + + '
Lineage & audit
  • Parent: ' + escapeHTML(parent ? parent.label : "none · root") + "
  • Direct descendants: " + childCount + "
  • Output origin: " + escapeHTML(origin) + "
  • Timestamp: " + escapeHTML(state.lastUpdated || "awaiting update") + "
  • Hash: " + escapeHTML(node.metadata.outputHash || (program ? program.hash : "unreported")) + "
" + payloadSection; return; } @@ -1205,7 +1366,191 @@ // null (missing shelf), never undefined, or dominates()/renderParetoPlot misbehave. var WIRE_METRIC_KEYS = ["boldness", "evidence", "plausibility", "rnpv", "positive", "impact", "recruit", "duration", "screens", "risk", "support", "occupancy", "convergence", "tractability_fit"]; + function finiteNumber(value) { + return typeof value === "number" && Number.isFinite(value) ? value : null; + } + + function normalizedScale(value, maximum) { + var number = finiteNumber(value); + if (number === null) return null; + return number >= 0 && number <= 1 ? number * maximum : number; + } + + function dollarsToMillions(value) { + var number = finiteNumber(value); + return number === null ? null : Math.round(number / 10000) / 100; + } + + function scientificHypothesisProjection(branch) { + var node = scientificNodeForStage(branch, "hypothesis"); + var artifact = node && node.artifact && typeof node.artifact === "object" + ? node.artifact + : {}; + var document = artifact.hypothesis && typeof artifact.hypothesis === "object" + ? artifact.hypothesis + : {}; + var hypothesis = document.hypothesis && typeof document.hypothesis === "object" + ? document.hypothesis + : {}; + var cards = artifact.cards && typeof artifact.cards === "object" ? artifact.cards : {}; + var firstCard = Array.isArray(cards.hypotheses) && cards.hypotheses.length + ? cards.hypotheses[0] + : {}; + var scores = hypothesis.scores && typeof hypothesis.scores === "object" + ? hypothesis.scores + : (firstCard.scores && typeof firstCard.scores === "object" ? firstCard.scores : {}); + var articulation = hypothesis.articulation && typeof hypothesis.articulation === "object" + ? hypothesis.articulation + : {}; + var statement = articulation.statement || articulation.mechanism || null; + var focus = branch.focus && typeof branch.focus === "object" ? branch.focus : {}; + var fallback = focus.display_label || focus.name || branch.branch_id || "Scientific branch"; + return { + id: scientificCandidateId(branch) || branch.branch_id, + label: statement || fallback, + short: (focus.display_label || fallback) + (statement ? " · " + statement : ""), + publicWhy: articulation.novel_because || statement || "Native hypothesis artifact; inspect the producer output and lineage.", + metrics: { + boldness: normalizedScale(scores.novelty, 10), + evidence: normalizedScale(scores.support, 100), + plausibility: normalizedScale(scores.testability, 100) + }, + uncertainty: cards.interpretability && cards.interpretability.uncertainty + ? cards.interpretability.uncertainty.method || "native hypothesis uncertainty attached" + : "native hypothesis uncertainty not summarized" + }; + } + + function scientificRoiProjection(branch) { + var node = scientificNodeForStage(branch, "roi"); + var artifact = node && node.artifact && typeof node.artifact === "object" + ? node.artifact + : {}; + var payload = artifact.payload && typeof artifact.payload === "object" ? artifact.payload : artifact; + var summary = payload.summary && typeof payload.summary === "object" ? payload.summary : {}; + var p10 = dollarsToMillions(summary.p10_rnpv); + var p50 = dollarsToMillions(summary.p50_rnpv); + var p90 = dollarsToMillions(summary.p90_rnpv); + var uncertainty = p10 !== null && p90 !== null + ? "rNPV P10–P90: " + metricCell(p10, "$", "M") + " to " + metricCell(p90, "$", "M") + : "native ROI uncertainty not summarized"; + return { + rnpv: p50, + positive: normalizedScale(summary.probability_positive_rnpv, 100), + impact: null, + uncertainty: uncertainty + }; + } + + function scientificStationPayloads(branch) { + var payloads = {}; + ["hypothesis", "roi", "recruitability", "simulation"].forEach(function (stageId) { + var node = scientificNodeForStage(branch, stageId); + if (node && node.artifact && typeof node.artifact === "object") { + payloads[stageId] = node.artifact; + } + }); + return payloads; + } + + function translateScientificWire(ws) { + var branches = Array.isArray(ws.branches) ? ws.branches : []; + var biomarkers = branches.map(function (branch, index) { + var focus = branch.focus && typeof branch.focus === "object" ? branch.focus : {}; + return { + slot: index, + id: "bio-slot-" + index, + label: focus.display_label || focus.name || focus.thing_id || branch.branch_id, + summary: (focus.kind === "process" ? "Mechanistic/PD readout focus" : "Biomarker focus") + " selected from live evidence; " + (focus.support_count || 0) + " supporting finding(s).", + metrics: { exploration: null, evidence: null, pursuit: null }, + nativeMetrics: { exploration: null, evidence: null, pursuit: null }, + uncertainty: "Focus selection preserves mapper finding and link IDs; no display score is imputed.", + displayMetricBasis: null, + displayMetricNote: null, + stationPayload: null, + scientificFocus: focus, + branchId: branch.branch_id + }; + }); + var programs = branches.map(function (branch, index) { + var hypothesis = scientificHypothesisProjection(branch); + var roi = scientificRoiProjection(branch); + var clinicalNode = scientificNodeForStage(branch, "recruitability"); + var simulationNode = scientificNodeForStage(branch, "simulation"); + var metrics = { + boldness: hypothesis.metrics.boldness, + evidence: hypothesis.metrics.evidence, + plausibility: hypothesis.metrics.plausibility, + rnpv: roi.rnpv, + positive: roi.positive, + impact: roi.impact, + recruit: null, + duration: null, + screens: null, + risk: null, + support: null, + occupancy: null, + convergence: null, + tractability_fit: null + }; + var program = { + id: branch.branch_id || "scientific-branch-" + (index + 1), + candidateId: hypothesis.id, + lane: index, + biomarkerSlot: index, + hypothesisSlot: 0, + hypothesisNodeId: "hyp-slot-" + index, + roiNodeId: "roi-slot-" + index, + recruitNodeId: "recruitability-slot-" + index, + simulationNodeId: "simulation-slot-" + index, + label: hypothesis.label, + short: hypothesis.short, + metrics: metrics, + nativeMetrics: Object.assign({}, metrics), + uncertainty: roi.uncertainty, + hypothesisUncertainty: hypothesis.uncertainty, + recruitmentUncertainty: "native clinical output not summarized", + tractabilityUncertainty: "Native dossier is categorical; no scalar tractability score is imputed.", + displayMetricBasis: null, + displayMetricNote: null, + publicWhy: hypothesis.publicWhy, + roiFailed: !scientificNodeForStage(branch, "roi") || scientificNodeForStage(branch, "roi").status !== "COMPLETE", + recruitFailed: !clinicalNode || clinicalNode.status !== "COMPLETE", + overflowRnpv: false, + notAmenable: Boolean(simulationNode && simulationNode.artifact && simulationNode.artifact.verdict === "not_tractable"), + revision: "scientific-packet-v1", + hash: (scientificNodeForStage(branch, "hypothesis") || {}).output_hash || "unhashed", + stationPayloads: scientificStationPayloads(branch), + scientificBranch: branch, + scientificNodes: branch.nodes || {}, + branchStatus: branch.status + }; + applyStationDerivations(program); + program.nativeMetrics = Object.assign({}, program.metrics); + return program; + }); + var stageRows = {}; + (ws.stages || []).forEach(function (stage) { + if (!stage || !stage.stage_id) return; + var mapped = Object.assign({}, stage, { stage_id: scientificStageId(stage.stage_id) }); + stageRows[mapped.stage_id] = mapped; + }); + var requestedLanes = Math.max( + state.snapshot && Number.isInteger(state.snapshot.biomarkers) ? state.snapshot.biomarkers : 0, + branches.length + ); + return { + biomarkers: biomarkers, + programs: programs, + stageRows: stageRows, + requestedLanes: requestedLanes, + biomarkerShortfall: Math.max(0, requestedLanes - biomarkers.length), + hypothesisShortfall: Math.max(0, requestedLanes - programs.length) + }; + } + function translateWire(ws) { + if (isScientificSnapshot(ws)) return translateScientificWire(ws); var biomarkers = (ws.biomarkers || []).map(function (item) { var nativeMetrics = Object.assign({}, item.metrics || {}); var displayMetrics = item.display_metric_basis === "REPRESENTATIVE_DEMO_SCENARIO_V1" && item.display_metrics @@ -1279,12 +1624,53 @@ }; } + function applyScientificSnapshotChrome(ws) { + state.representativeDemo = + ws.representative_demo === true && ws.presentation_mode === "REPRESENTATIVE_DEMO"; + state.scientificPacketExcludesRepresentativeValues = + ws.scientific_packet_excludes_representative_values === true; + state.presentationMode = ws.presentation_mode || null; + state.executionMode = ws.execution_mode || null; + elements.representativeWatermark.hidden = !state.representativeDemo; + elements.representativeWatermark.textContent = state.representativeDemo + ? (ws.watermark || "REPRESENTATIVE DEMO VALUES") + : ""; + document.getElementById("scenario-profile").disabled = true; + document.getElementById("highlander-mode-description").textContent = "Server-native producer packet comparison"; + document.getElementById("highlander-mode-chip").textContent = "SERVER HIGHLANDER"; + document.getElementById("highlander-mode-chip").classList.remove("mock"); + document.getElementById("highlander-server-chip").textContent = "RFC 8785 PACKETS"; + document.getElementById("comparison-mode-badge").textContent = "server result"; + document.getElementById("gap-confirm-copy").textContent = "I acknowledge terminal producer failures remain visible and incomparable. Run the pinned server Highlander consumer."; + document.getElementById("module-dialog-summary").textContent = "This scientific run exposes exact per-branch producer artifacts, refs, hashes, origins, terminal reasons, and the server Highlander result."; + } + function ingestSnapshot(ws) { var hadPrograms = Boolean(state.runData && state.runData.programs && state.runData.programs.length); + var scientific = isScientificSnapshot(ws); + var rebuildScientificScaffold = scientific && state.snapshot && state.snapshot.hypotheses !== 1; + state.scientificSnapshot = scientific; + if (scientific) applyScientificSnapshotChrome(ws); + else { + state.representativeDemo = false; + elements.representativeWatermark.hidden = true; + elements.representativeWatermark.textContent = ""; + } state.runData = translateWire(ws); + if (rebuildScientificScaffold) { + state.snapshot = Object.freeze(Object.assign({}, state.snapshot, { + biomarkers: Math.max(state.snapshot.biomarkers, state.runData.biomarkers.length), + hypotheses: 1 + })); + state.runData.requestedLanes = state.snapshot.biomarkers; + state.runData.biomarkerShortfall = Math.max(0, state.snapshot.biomarkers - state.runData.biomarkers.length); + state.runData.hypothesisShortfall = Math.max(0, state.snapshot.biomarkers - state.runData.programs.length); + buildScaffold(); + } var stageIds = STAGES.map(function (stage) { return stage.id; }); (ws.stages || []).forEach(function (stage) { - var index = stageIds.indexOf(stage.stage_id); + var mappedStageId = scientific ? scientificStageId(stage.stage_id) : stage.stage_id; + var index = stageIds.indexOf(mappedStageId); if (index === -1) return; var truth = normalizeStageTruth(stage, { execution: "QUEUED" }); var status = truth.presentationStatus; @@ -1294,9 +1680,30 @@ status === "COMPLETE_WITH_WARNINGS" ? "warning" : status === "FAILED" ? "failed" : "queued"; state.stageNotes[index] = stage.note || status.toLowerCase(); - if (status === "RUNNING") markStagePending(stage.stage_id); - if (status === "COMPLETE" || status === "COMPLETE_WITH_WARNINGS" || status === "FAILED") bindStage(stage.stage_id); + if (status === "RUNNING") markStagePending(mappedStageId); + if (status === "COMPLETE" || status === "COMPLETE_WITH_WARNINGS" || status === "FAILED") bindStage(mappedStageId); }); + if (scientific) { + var highlander = ws.highlander && typeof ws.highlander === "object" ? ws.highlander : {}; + state.highlanderLaunched = highlander.launched === true; + state.highlanderResult = highlander.result && typeof highlander.result === "object" ? highlander.result : null; + state.highlanderResultHash = highlander.result_hash || null; + var packet = highlander.packet_snapshot; + if (packet && typeof packet === "object") { + state.packetSnapshot = packet.id || packet.snapshotId || state.packetSnapshot; + } else if (typeof packet === "string") { + state.packetSnapshot = packet; + } + if (state.highlanderResult && state.highlanderResult.snapshotId) { + state.packetSnapshot = state.highlanderResult.snapshotId; + } + document.getElementById("packet-snapshot").textContent = state.packetSnapshot || "awaiting server packet"; + var highlanderNav = document.querySelector('[data-nav="highlander"]'); + if (state.highlanderLaunched && highlanderNav) { + highlanderNav.disabled = false; + highlanderNav.classList.remove("locked"); + } + } renderProgress(); renderGraph(); if (!hadPrograms && state.runData.programs.length) centerGraphOnActiveLineage(); @@ -1326,6 +1733,13 @@ state.inspectorCollapsed = false; state.highlanderReady = false; state.highlanderLaunched = false; + state.highlanderLaunching = false; + state.highlanderResult = null; + state.highlanderResultHash = null; + state.scientificSnapshot = false; + state.representativeDemo = false; + elements.representativeWatermark.hidden = true; + elements.representativeWatermark.textContent = ""; elements.gapConfirmInput.checked = false; var highlanderNav = document.querySelector('[data-nav="highlander"]'); if (highlanderNav) { @@ -1415,7 +1829,9 @@ function renderReadiness() { var nonterminal = state.stageStates.filter(function (stage) { return stage === "queued" || stage === "running"; }).length; var programCount = state.runData ? state.runData.programs.length : 0; - var launchName = BOOT.mode === "http" ? "Open client-side comparison" : "Launch Highlander"; + var launchName = state.scientificSnapshot + ? (state.highlanderLaunched ? "Open server Highlander result" : "Run server Highlander") + : (BOOT.mode === "http" ? "Open client-side comparison" : "Launch Highlander"); if (!programCount) { elements.readinessState.textContent = "BLOCKED · no candidates"; elements.packetCounts.innerHTML = '0 complete0 partial0 blockedno candidates'; @@ -1432,13 +1848,19 @@ elements.launchHighlander.textContent = launchName + " · blocked"; return; } - elements.readinessState.textContent = "READY WITH TERMINAL GAPS · no nonterminal records"; - elements.packetCounts.innerHTML = '0 complete' + programCount + ' partial0 blocked0 nonterminal'; + var completeCount = state.scientificSnapshot + ? state.runData.programs.filter(function (program) { return program.branchStatus === "COMPLETE"; }).length + : 0; + var blockedCount = state.scientificSnapshot ? programCount - completeCount : 0; + elements.readinessState.textContent = state.scientificSnapshot + ? "SERVER HIGHLANDER READY · terminal producer packets" + : "READY WITH TERMINAL GAPS · no nonterminal records"; + elements.packetCounts.innerHTML = '' + completeCount + ' complete' + (state.scientificSnapshot ? 0 : programCount) + ' partial' + blockedCount + ' blocked0 nonterminal'; elements.gapConfirm.classList.add("visible"); elements.launchHighlander.disabled = !elements.gapConfirmInput.checked; elements.launchHighlander.textContent = elements.gapConfirmInput.checked ? launchName + " →" - : (BOOT.mode === "http" ? "Acknowledge gaps to continue" : "Acknowledge gaps to launch"); + : (state.scientificSnapshot ? "Acknowledge terminal packets to run" : (BOOT.mode === "http" ? "Acknowledge gaps to continue" : "Acknowledge gaps to launch")); } function resetDemo() { @@ -1534,6 +1956,28 @@ announce("Run creation failed. Inputs preserved; retry available."); } + function attachConfiguredRun(runId) { + if (BOOT.mode !== "http" || !runId) return; + var validation = validateSetup(); + if (!validation.valid) { + submitFailed("Cannot attach until the visible setup fields are valid."); + return; + } + validation.biomarkerRange = [Number(document.getElementById("biomarker-low").value), Number(document.getElementById("biomarker-high").value)]; + validation.hypothesisRange = [Number(document.getElementById("hypothesis-low").value), Number(document.getElementById("hypothesis-high").value)]; + if (BOOT.launchMode === "scientific") validation.hypotheses = 1; + state.runData = { + biomarkers: [], + programs: [], + requestedLanes: validation.biomarkers * validation.hypotheses, + biomarkerShortfall: 0, + hypothesisShortfall: 0 + }; + enterRun(runId, validation); + elements.snapshotNote.textContent = "Attached read-only to backend run " + runId + ". No new run was created."; + startHttpRun(runId); + } + function submitRun(event) { event.preventDefault(); var validation = validateSetup(); @@ -1548,29 +1992,35 @@ validateSetup(); if (BOOT.mode === "http") { - var setupWire = { - clinical_indication: { submitted_text: validation.indication }, - biomarker_exploration_range: { - lower: validation.biomarkerRange[0], - upper: validation.biomarkerRange[1] - }, - maximum_biomarkers: validation.biomarkers, - maximum_literature_papers: validation.papers, - hypothesis_boldness_range: { - lower: validation.hypothesisRange[0], - upper: validation.hypothesisRange[1] - }, - maximum_hypotheses_per_biomarker: validation.hypotheses - }; + var setupWire = BOOT.launchMode === "scientific" + ? buildScientificSetup(validation) + : { + clinical_indication: { submitted_text: validation.indication }, + biomarker_exploration_range: { + lower: validation.biomarkerRange[0], + upper: validation.biomarkerRange[1] + }, + maximum_biomarkers: validation.biomarkers, + maximum_literature_papers: validation.papers, + hypothesis_boldness_range: { + lower: validation.hypothesisRange[0], + upper: validation.hypothesisRange[1] + }, + maximum_hypotheses_per_biomarker: validation.hypotheses + }; BOOT.http.createRun(setupWire).then(function (created) { - if (!created || !created.run || !created.run.run_id) { + var createdRunId = created && created.run && created.run.run_id + ? created.run.run_id + : (created && created.runId ? created.runId : null); + if (!createdRunId) { submitFailed("Backend responded without a run_id."); return; } + if (BOOT.launchMode === "scientific") validation.hypotheses = 1; state.runData = { biomarkers: [], programs: [], requestedLanes: validation.biomarkers * validation.hypotheses, biomarkerShortfall: 0, hypothesisShortfall: 0 }; state.selectedProgramId = null; - enterRun(created.run.run_id, validation); - startHttpRun(created.run.run_id); + enterRun(createdRunId, validation); + startHttpRun(createdRunId); }).catch(function (error) { submitFailed("Run creation failed against " + BOOT.base + " (" + String(error && error.message) + ")."); }); @@ -1617,6 +2067,14 @@ } function programStatus(program) { + if (state.scientificSnapshot) { + var serverStatus = scientificComparisonStatus(state.highlanderResult, program.candidateId); + return serverStatus === "FRONTIER" + ? "non-dominated" + : serverStatus === "DOMINATED" + ? "dominated" + : "incomparable"; + } var vector = paretoVector(program); if (![vector.roi, vector.recruitability, vector.simulation].every(Number.isFinite)) return "incomparable"; var dominated = state.runData.programs.some(function (other) { @@ -1631,6 +2089,7 @@ var statusOrder = { "non-dominated": 0, incomparable: 1, dominated: 2 }; var statusDifference = statusOrder[programStatus(a)] - statusOrder[programStatus(b)]; if (statusDifference) return statusDifference; + if (state.scientificSnapshot) return String(a.candidateId || a.id).localeCompare(String(b.candidateId || b.id)); if (state.scenario === "speed") return (b.metrics.recruit || -1) - (a.metrics.recruit || -1); if (state.scenario === "capital") return (b.metrics.positive || -1) - (a.metrics.positive || -1); return (b.metrics.plausibility || -1) - (a.metrics.plausibility || -1); @@ -1638,13 +2097,89 @@ return copy; } + function serverStatusLabel(program) { + return scientificComparisonStatus(state.highlanderResult, program.candidateId) || "AWAITING_SERVER_HIGHLANDER"; + } + + function serverCandidateObjectives(program) { + var candidate = scientificHighlanderCandidate(state.highlanderResult, program.candidateId); + if (!candidate || !Array.isArray(candidate.observations)) return []; + return candidate.observations.map(function (observation) { + return { + id: observation.objectiveId || "unreported objective", + value: observation.rawValue, + unit: observation.unit || "", + direction: observation.direction || "", + basis: observation.evidenceBasis || "UNREPORTED" + }; + }); + } + + function formatServerObjectives(program) { + var objectives = serverCandidateObjectives(program); + if (!objectives.length) return "No comparable server objectives returned."; + return objectives.map(function (objective) { + return objective.id + " = " + JSON.stringify(objective.value) + (objective.unit ? " " + objective.unit : "") + " · " + objective.direction + " · " + objective.basis; + }).join("; "); + } + function metricCell(value, prefix, suffix) { if (value === null || typeof value !== "number") return 'MISSING'; if (prefix === "$" && value < 0) return "-$" + Math.abs(value) + (suffix || ""); return (prefix || "") + value + (suffix || ""); } + function renderServerHighlanderResult() { + if (!state.scientificSnapshot) { + elements.serverHighlanderResult.hidden = true; + elements.serverHighlanderResult.innerHTML = ""; + return; + } + elements.serverHighlanderResult.hidden = false; + var result = state.highlanderResult; + if (!result) { + elements.serverHighlanderResult.innerHTML = + '

Server Highlander result

No browser frontier is substituted.

awaiting launch
' + + '

Terminal producer packets are ready. Launch the pinned server consumer to create the comparison snapshot.

'; + return; + } + var frontier = Array.isArray(result.frontier) ? result.frontier : []; + var dominated = Array.isArray(result.dominated) ? result.dominated : []; + var incomparable = Array.isArray(result.incomparable) ? result.incomparable : []; + var action = result.nextEvidenceAction && typeof result.nextEvidenceAction === "object" + ? result.nextEvidenceAction + : null; + var actionHtml = action + ? '
Producer-grounded next evidence action' + escapeHTML(action.actionType || "action") + " · " + escapeHTML(action.target || "unreported target") + '

' + escapeHTML(action.description || "No description supplied.") + '

Producer: ' + escapeHTML(action.producerModuleId || "unreported") + " · output " + escapeHTML(action.producerOutputSha256 || "unreported") + " · candidates " + escapeHTML(Array.isArray(action.candidateIds) ? action.candidateIds.join(", ") : "unreported") + "

" + : '

No producer emitted a grounded next evidence action.

'; + elements.serverHighlanderResult.innerHTML = + '

Server Highlander result

Pareto membership comes from the pinned packet consumer, not browser display values.

server native
' + + '
' + + '
Frontier' + escapeHTML(frontier.length ? frontier.join(", ") : "none") + "
" + + '
Dominated' + escapeHTML(dominated.length ? dominated.join(", ") : "none") + "
" + + '
Incomparable' + escapeHTML(incomparable.length ? incomparable.map(function (item) { return item.candidateId; }).join(", ") : "none") + "
" + + "
" + + '

Packet: ' + escapeHTML(state.packetSnapshot || "unreported") + " · request/result hash: " + escapeHTML(state.highlanderResultHash || "unreported") + " · result snapshot: " + escapeHTML(result.snapshotId || "unreported") + "

" + + actionHtml; + } + function renderScenario() { + if (state.scientificSnapshot) { + var result = state.highlanderResult; + var policy = result && result.objectivePolicy && typeof result.objectivePolicy === "object" + ? result.objectivePolicy + : null; + elements.scenarioMeta.innerHTML = 'Server objective policy
' + escapeHTML(policy ? (policy.policyId || result.objectivePolicyId || "unreported") : "awaiting server Highlander") + '
Presentation controls are disabled for scientific comparison.'; + elements.weightList.innerHTML = ""; + var objectives = policy && Array.isArray(policy.objectives) ? policy.objectives : []; + objectives.forEach(function (objective) { + var row = document.createElement("div"); + row.className = "weight-row"; + row.innerHTML = '' + escapeHTML(objective.objectiveId || "unreported") + " · " + escapeHTML(objective.direction || "unreported") + 'server rule'; + elements.weightList.appendChild(row); + }); + return; + } var scenario = SCENARIOS[state.scenario]; elements.scenarioMeta.innerHTML = '' + escapeHTML(scenario.name + " " + scenario.version) + '
' + escapeHTML(scenario.description) + '
Author/source: ' + escapeHTML(scenario.author) + " · " + escapeHTML(scenario.timestamp) + ""; elements.weightList.innerHTML = ""; @@ -1680,7 +2215,7 @@ button.className = "program-item" + (program.id === state.selectedProgramId ? " selected" : ""); var status = programStatus(program); button.dataset.programId = program.id; - button.innerHTML = "" + escapeHTML(program.short) + "" + escapeHTML(status) + " · packet r1 · selection is not a winner"; + button.innerHTML = "" + escapeHTML(program.short) + "" + escapeHTML(state.scientificSnapshot ? serverStatusLabel(program) : status) + " · " + escapeHTML(state.scientificSnapshot ? program.candidateId : "packet r1") + " · selection is not a winner"; button.addEventListener("click", function () { selectProgram(program.id); }); elements.programList.appendChild(button); }); @@ -1704,15 +2239,18 @@ var row = document.createElement("tr"); var status = programStatus(program); row.dataset.status = status; + var scientificObjectives = state.scientificSnapshot + ? '
' + escapeHTML(formatServerObjectives(program)) + "" + : ""; row.innerHTML = - '
' + escapeHTML(program.uncertainty) + "" + + '
' + escapeHTML(program.uncertainty) + "" + scientificObjectives + "" + "" + metricCell(program.metrics.rnpv, "$", "M") + (program.overflowRnpv ? '
above display domain' : "") + "" + "" + metricCell(program.metrics.positive, "", "%") + "" + "" + metricCell(program.metrics.recruit, "", "/100") + "" + "" + metricCell(program.metrics.duration, "", " mo") + "" + "" + metricCell(program.metrics.plausibility, "", "/100") + "" + "" + simulationComparisonCell(program) + "" + - "" + escapeHTML(status) + ""; + "" + escapeHTML(state.scientificSnapshot ? serverStatusLabel(program) : status) + ""; row.querySelector(".table-program").addEventListener("click", function () { selectProgram(program.id); }); elements.comparisonBody.appendChild(row); }); @@ -1720,6 +2258,41 @@ function renderParetoPlot() { var svg = elements.paretoPlot; + if (state.scientificSnapshot) { + document.getElementById("pareto-caption").innerHTML = "Server-native Pareto set: categorical membership from the pinned Highlander result. Positions below separate frontier, dominated, and incomparable packets; they do not encode browser-computed scientific magnitude."; + svg.setAttribute("data-pareto-dimensions", "server-native-objective-policy"); + svg.setAttribute("aria-label", "Server-native Highlander Pareto membership"); + svg.innerHTML = 'FRONTIERDOMINATEDINCOMPARABLE / AWAITING'; + var columnCounts = { "non-dominated": 0, dominated: 0, incomparable: 0 }; + state.runData.programs.forEach(function (program, index) { + var status = programStatus(program); + var columnX = status === "non-dominated" ? 150 : status === "dominated" ? 350 : 550; + var rowIndex = columnCounts[status] || 0; + columnCounts[status] = rowIndex + 1; + var y = 70 + rowIndex * 45; + var circle = document.createElementNS(svg.namespaceURI, "circle"); + circle.setAttribute("cx", String(columnX)); + circle.setAttribute("cy", String(y)); + circle.setAttribute("r", program.id === state.selectedProgramId ? "10" : "8"); + circle.setAttribute("class", "plot-point " + status + (program.id === state.selectedProgramId ? " selected" : "")); + circle.setAttribute("data-plan-id", program.id); + circle.setAttribute("data-server-status", serverStatusLabel(program)); + circle.setAttribute("tabindex", "0"); + circle.setAttribute("role", "button"); + circle.setAttribute("aria-label", "Candidate " + program.candidateId + "; server status " + serverStatusLabel(program)); + circle.addEventListener("click", function () { selectProgram(program.id); }); + svg.appendChild(circle); + var label = document.createElementNS(svg.namespaceURI, "text"); + label.setAttribute("x", String(columnX + 16)); + label.setAttribute("y", String(y + 3)); + label.setAttribute("class", "plot-server-label"); + label.textContent = String(index + 1) + " · " + program.candidateId; + svg.appendChild(label); + }); + return; + } + document.getElementById("pareto-caption").innerHTML = 'Three-dimensional Pareto view: P50 rNPV × recruitability × simulation / tractability. Each numbered point is one plan. Plans with identical vectors fan slightly around their shared coordinate. Nominal frontier projection The line is a projected guide through complete non-dominated records, not a frontier surface or decision threshold. Missing values remain on a separate shelf. The current RA demo uses a labeled representative tractability fit on the Z axis; its native cached dossier remains shared across plans.'; + svg.setAttribute("data-pareto-dimensions", "roi,recruitability,simulation"); svg.innerHTML = 'P50 rNPV · $M modeled →Recruitability /100 →Simulation / tractability /100 →missing objective shelf · not plotted as zero'; var planCount = state.runData.programs.length; var plottedPrograms = state.runData.programs.map(function (program, index) { @@ -1827,12 +2400,37 @@ return state.runData.programs.find(function (program) { return program.id === state.selectedProgramId; }) || state.runData.programs[0] || null; } + function renderScientificProgramDetail(program) { + var candidate = scientificHighlanderCandidate(state.highlanderResult, program.candidateId); + var status = serverStatusLabel(program); + var qualifiers = candidate && Array.isArray(candidate.qualifiers) ? candidate.qualifiers : []; + var incomparable = candidate && Array.isArray(candidate.incomparableReasons) + ? candidate.incomparableReasons + : []; + var nodeRows = ["hypothesis", "recruitability", "simulation", "roi"].map(function (stageId) { + var node = scientificNodeForStage(program.scientificBranch, stageId); + if (!node) return "
  • " + escapeHTML(stageId) + ": not returned
  • "; + return "
  • " + escapeHTML(node.module_id || scientificModuleId(stageId)) + ": " + escapeHTML(node.status || "UNREPORTED") + " · " + escapeHTML(node.output_origin || "UNREPORTED") + " · " + escapeHTML(node.reason_code || "no reason code") + "
    " + escapeHTML(node.output_hash || "no output hash") + "
  • "; + }).join(""); + elements.programDetail.innerHTML = + '
    ' + escapeHTML(program.candidateId + " · " + status) + '

    ' + escapeHTML(program.publicWhy) + "

    " + + '
    Server status' + escapeHTML(status) + '
    Branch status' + escapeHTML(program.branchStatus || "UNREPORTED") + '
    Output hash' + escapeHTML(program.hash) + "
    " + + '
    Server objective observations

    ' + escapeHTML(formatServerObjectives(program)) + "

    Qualifiers: " + escapeHTML(qualifiers.length ? qualifiers.join(" · ") : "none reported") + "

    Incomparable reasons: " + escapeHTML(incomparable.length ? incomparable.join(" · ") : "none reported") + "

    " + + "
    Producer terminal packets
      " + nodeRows + "
    " + + '
    Comparison provenance
    • Packet snapshot: ' + escapeHTML(state.packetSnapshot || "unreported") + '
    • Result hash: ' + escapeHTML(state.highlanderResultHash || "unreported") + '
    • Representative values excluded from scientific packet: ' + escapeHTML(state.scientificPacketExcludesRepresentativeValues ? "yes" : "not confirmed") + "
    "; + document.getElementById("open-source-node").dataset.nodeId = program.hypothesisNodeId; + } + function renderProgramDetail() { var program = selectedProgram(); if (!program) { elements.programDetail.innerHTML = '
    No candidates exist, so no program can be selected.
    '; return; } + if (state.scientificSnapshot) { + renderScientificProgramDetail(program); + return; + } var status = programStatus(program); var simulationMetric = simulationParetoValue(program); var whyStatus = status === "non-dominated" @@ -1865,6 +2463,7 @@ function renderHighlander() { if (!state.runData) return; + renderServerHighlanderResult(); renderScenario(); renderProgramList(); renderComparison(); @@ -1960,6 +2559,37 @@ function launchHighlander() { if (!state.highlanderReady || !elements.gapConfirmInput.checked) return; + if (state.scientificSnapshot) { + if (state.highlanderLaunched) { + switchScreen("highlander"); + return; + } + if (state.highlanderLaunching) return; + state.highlanderLaunching = true; + elements.launchHighlander.disabled = true; + elements.launchHighlander.textContent = "Running server Highlander…"; + BOOT.http.launchHighlander(state.runId, true).then(function (response) { + var snapshot = response && response.scientific ? response.scientific : response; + if (!isScientificSnapshot(snapshot)) { + throw new Error("server response did not include labrador.scientific-snapshot.v1"); + } + ingestSnapshot(snapshot); + if (!state.highlanderLaunched) { + throw new Error("server did not mark the Highlander job launched"); + } + showToast(state.highlanderResult + ? "Server Highlander result loaded from " + (state.packetSnapshot || "the pinned packet") + "." + : "Server launch completed without a comparison result; no browser substitute was created."); + switchScreen("highlander"); + }).catch(function (error) { + showToast("Server Highlander could not complete: " + String(error && error.message)); + announce("Server Highlander failed. Terminal producer packets remain visible; no browser comparison was substituted."); + }).finally(function () { + state.highlanderLaunching = false; + renderReadiness(); + }); + return; + } if (!state.highlanderLaunched) { state.highlanderLaunched = true; var nav = document.querySelector('[data-nav="highlander"]'); @@ -2142,6 +2772,43 @@ "Recruitabilityclinical_simulationA failed live attempt and schema-valid DEMO_FALLBACK remain separate truths." + "TractabilitysimulationValidated cached output is inspectable; no scalar atomistic score is imputed." + "Highlanderhypothesis-highlanderCLIENT-SIDE COMPARISON · SERVER CONSUMER NOT WIRED."; + + if (BOOT.launchMode === "scientific") { + setupModeChip.textContent = "Scientific replay · explicit IRAK4 preset"; + elements.runButton.textContent = "Run scientific branch pipeline →"; + elements.maxHypotheses.value = "1"; + elements.maxHypotheses.disabled = true; + state.metrics.simulation = "support"; + document.querySelectorAll('[data-metric-stage="simulation"]').forEach(function (button) { + if (button.dataset.metricValue === "tractability_fit") { + button.dataset.metricValue = "support"; + button.textContent = "Native dossier (categorical)"; + button.hidden = false; + button.setAttribute("aria-pressed", "true"); + } else { + button.hidden = true; + button.setAttribute("aria-pressed", "false"); + } + }); + document.getElementById("simulation-axis-source").innerHTML = "categorical
    native artifact"; + document.getElementById("highlander-mode-description").textContent = "Server-native producer packet comparison"; + document.getElementById("highlander-mode-chip").textContent = "SERVER HIGHLANDER"; + document.getElementById("highlander-server-chip").textContent = "AWAITING PACKETS"; + document.getElementById("comparison-mode-badge").textContent = "server result"; + document.getElementById("gap-confirm-copy").textContent = "I acknowledge terminal producer failures remain visible and incomparable. Run the pinned server Highlander consumer."; + document.getElementById("module-dialog-summary").textContent = "Explicit scientific mode uses the checked-in IRAK4/RA v3 setup frame, deterministic replay, one HypGen run per evidence focus, and server-native Highlander."; + document.querySelector("#module-dialog .module-table tbody").innerHTML = + "Evidence mappingresearch-evidence-mapperOne explicit replay; only real biomarker and supported process focuses become branches." + + "Hypothesis generationHypothesis_GeneratorOne full provider-shaped replay per selected focus." + + "ROI / impactrnpv-roi-calculatorNative output uses the separate analyst-supplied valuation frame." + + "Recruitabilityclinical_simulationNative simulated_* fields remain unchanged and visible." + + "TractabilitysimulationNative categorical dossier; no browser scalar is invented." + + "Highlanderhypothesis-highlanderPinned server consumer; packet hashes, Pareto membership, and next evidence action are rendered verbatim."; + } + if (BOOT.runId) { + setupModeChip.textContent = "Attach read-only · " + BOOT.runId; + elements.runButton.textContent = "Attach to configured run →"; + } } function initialize() { @@ -2151,6 +2818,7 @@ applyBootMode(); validateSetup(); renderProgress(); + if (BOOT.runId) attachConfiguredRun(BOOT.runId); } initialize(); diff --git a/app/js/backend-http.js b/app/js/backend-http.js index 12b1322..ba67dcd 100644 --- a/app/js/backend-http.js +++ b/app/js/backend-http.js @@ -61,6 +61,19 @@ export function createHttpBackend(baseUrl) { ); }, + // POST /api/runs/:id/highlander — runs the pinned server consumer and + // returns the refreshed server state. Scientific mode never computes a + // substitute frontier in the browser. + async launchHighlander(runId, acknowledgeGaps) { + return requestJson( + `${base}/api/runs/${encodeURIComponent(runId)}/highlander`, + { + method: "POST", + body: JSON.stringify({ acknowledgeGaps: acknowledgeGaps === true }), + }, + ); + }, + // Poll the snapshot every 5s until every stage is terminal. // onSnapshot(snapshot) on every successful fetch; // onFreshness({state, failures}) on transport-state changes. diff --git a/app/js/snapshot-contract.js b/app/js/snapshot-contract.js index 0b0a9e7..8d90e9b 100644 --- a/app/js/snapshot-contract.js +++ b/app/js/snapshot-contract.js @@ -78,3 +78,104 @@ export function interpretabilityView(payload) { stepCount: Array.isArray(raw.steps) ? raw.steps.length : 0, }; } + +const SCIENTIFIC_STAGE_BY_MODULE = Object.freeze({ + evidence_mapper: "biomarker", + hypothesis_generator: "hypothesis", + roi_calculator: "roi", + clinical_simulation: "recruitability", + simulation: "simulation", +}); + +const SCIENTIFIC_MODULE_BY_STAGE = Object.freeze( + Object.fromEntries( + Object.entries(SCIENTIFIC_STAGE_BY_MODULE).map(([moduleId, stageId]) => [ + stageId, + moduleId, + ]), + ), +); + +export function isScientificSnapshot(snapshot) { + return Boolean( + snapshot && + typeof snapshot === "object" && + snapshot.schema_version === "labrador.scientific-snapshot.v1", + ); +} + +export function scientificStageId(moduleId) { + return SCIENTIFIC_STAGE_BY_MODULE[moduleId] || moduleId || null; +} + +export function scientificModuleId(stageId) { + return SCIENTIFIC_MODULE_BY_STAGE[stageId] || stageId || null; +} + +export function scientificNodeForStage(branch, stageId) { + if (!branch || typeof branch !== "object") return null; + const nodes = branch.nodes; + if (!nodes || typeof nodes !== "object" || Array.isArray(nodes)) return null; + const node = nodes[scientificModuleId(stageId)]; + return node && typeof node === "object" && !Array.isArray(node) ? node : null; +} + +export function scientificCandidateId(branch) { + const node = scientificNodeForStage(branch, "hypothesis"); + const artifact = node && node.artifact; + if (artifact && typeof artifact === "object") { + const document = artifact.hypothesis; + const hypothesis = + document && typeof document === "object" && document.hypothesis && + typeof document.hypothesis === "object" + ? document.hypothesis + : null; + if (hypothesis && typeof hypothesis.id === "string" && hypothesis.id.trim()) { + return hypothesis.id; + } + const cards = artifact.cards; + if (cards && Array.isArray(cards.hypotheses)) { + const card = cards.hypotheses.find( + (item) => item && typeof item.id === "string" && item.id.trim(), + ); + if (card) return card.id; + } + } + return branch && typeof branch.branch_id === "string" ? branch.branch_id : null; +} + +export function scientificHighlanderCandidate(result, candidateId) { + if (!result || typeof result !== "object" || !candidateId) return null; + if (!Array.isArray(result.candidates)) return null; + return ( + result.candidates.find( + (candidate) => + candidate && + typeof candidate === "object" && + candidate.candidateId === candidateId, + ) || null + ); +} + +export function scientificComparisonStatus(result, candidateId) { + const candidate = scientificHighlanderCandidate(result, candidateId); + if (candidate && typeof candidate.comparisonStatus === "string") { + return candidate.comparisonStatus; + } + if (!result || typeof result !== "object" || !candidateId) return null; + if (Array.isArray(result.frontier) && result.frontier.includes(candidateId)) { + return "FRONTIER"; + } + if (Array.isArray(result.dominated) && result.dominated.includes(candidateId)) { + return "DOMINATED"; + } + if ( + Array.isArray(result.incomparable) && + result.incomparable.some( + (item) => item && typeof item === "object" && item.candidateId === candidateId, + ) + ) { + return "INCOMPARABLE"; + } + return null; +} diff --git a/app/styles.css b/app/styles.css index acdc4f9..37a1cf4 100644 --- a/app/styles.css +++ b/app/styles.css @@ -51,6 +51,24 @@ line-height: 1.45; } + .representative-watermark { + position: fixed; + z-index: 1200; + inset: auto 18px 18px auto; + padding: 9px 13px; + color: #4f2e00; + background: rgba(255, 217, 143, .96); + border: 2px solid var(--amber); + border-radius: 8px; + box-shadow: var(--shadow); + font-size: 11px; + font-weight: 950; + letter-spacing: .08em; + pointer-events: none; + } + + .representative-watermark[hidden] { display: none; } + button, input, select, textarea { font: inherit; } button, select, input[type="range"] { cursor: pointer; } @@ -886,6 +904,19 @@ .inspector-section summary { padding: 10px 0 6px; cursor: pointer; font-size: 10px; font-weight: 900; } .inspector-section p, .inspector-section li { color: #4f5953; font-size: 9px; } .inspector-section ul { margin: 4px 0 8px; padding-left: 17px; } + .native-artifact { + max-height: 340px; + margin: 4px 0 10px; + padding: 10px; + overflow: auto; + color: #27342e; + background: #f0eee5; + border: 1px solid #d0cab9; + border-radius: 8px; + font-size: 9px; + white-space: pre-wrap; + word-break: break-word; + } .interpretability-view { margin: 10px 0; padding: 10px; background: #eef4e9; border: 1px solid #b7c7ad; border-radius: 8px; } .interpretability-view > section h4 { margin: 0 0 4px; font-size: 13px; } .interpretability-view > section p { margin: 4px 0; } @@ -906,6 +937,17 @@ /* Screen 3 */ .highlander-screen { background: #ede8da; } + .server-highlander-result[hidden] { display: none; } + .server-result-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; } + .server-result-grid > div { min-width: 0; padding: 8px; background: #efede4; border-radius: 7px; } + .server-result-grid span { display: block; color: var(--muted); font-size: 7px; font-weight: 900; letter-spacing: .06em; text-transform: uppercase; } + .server-result-grid strong { display: block; overflow-wrap: anywhere; font-size: 10px; } + .next-evidence-action { margin-top: 10px; padding: 10px; background: var(--blue-soft); border-left: 4px solid var(--blue); border-radius: 7px; } + .next-evidence-action strong { display: block; margin-top: 3px; } + .next-evidence-action p { margin: 4px 0 0; font-size: 9px; } + .hash-clip { display: block; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .plot-server-label { fill: #4d5751; font-size: 8px; font-weight: 800; } + .highlander-head { min-height: 126px; display: grid; diff --git a/tests/backend-http.test.mjs b/tests/backend-http.test.mjs new file mode 100644 index 0000000..fa5c2b5 --- /dev/null +++ b/tests/backend-http.test.mjs @@ -0,0 +1,29 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { createHttpBackend } from "../app/js/backend-http.js"; + +test("scientific Highlander launch posts acknowledgement to the run-scoped endpoint", async () => { + const originalFetch = globalThis.fetch; + const calls = []; + globalThis.fetch = async (url, options) => { + calls.push({ url, options }); + return { + ok: true, + async json() { + return { scientific: { schema_version: "labrador.scientific-snapshot.v1" } }; + }, + }; + }; + try { + const backend = createHttpBackend("http://127.0.0.1:8787/"); + const response = await backend.launchHighlander("LR test/1", true); + assert.equal(response.scientific.schema_version, "labrador.scientific-snapshot.v1"); + assert.equal(calls.length, 1); + assert.equal(calls[0].url, "http://127.0.0.1:8787/api/runs/LR%20test%2F1/highlander"); + assert.equal(calls[0].options.method, "POST"); + assert.deepEqual(JSON.parse(calls[0].options.body), { acknowledgeGaps: true }); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tests/fixtures/orchestrator-scientific-snapshot.json b/tests/fixtures/orchestrator-scientific-snapshot.json new file mode 100644 index 0000000..c52dc53 --- /dev/null +++ b/tests/fixtures/orchestrator-scientific-snapshot.json @@ -0,0 +1,297 @@ +{ + "schema_version": "labrador.scientific-snapshot.v1", + "run_id": "LR-SCIENTIFIC-TEST", + "run_status": "COMPLETED_WITH_WARNINGS", + "updated_at": "2026-08-16T18:00:00Z", + "last_event_id": 42, + "execution_mode": "REPLAY", + "presentation_mode": "SCIENTIFIC", + "representative_demo": false, + "watermark": null, + "scientific_packet_excludes_representative_values": true, + "stages": [ + { + "stage_id": "evidence_mapper", + "result_status": "COMPLETE", + "execution_status": "COMPLETE", + "module_execution_status": "COMPLETE", + "output_origin": "DETERMINISTIC_REPLAY", + "result_basis": ["OBSERVED"], + "runtime_maturity": "LOCAL", + "reason_code": null, + "qualifiers": [], + "warnings": [], + "note": "one evidence graph complete" + }, + { + "stage_id": "hypothesis_generator", + "result_status": "COMPLETE_WITH_WARNINGS", + "execution_status": "COMPLETE_WITH_WARNINGS", + "module_execution_status": "COMPLETE", + "output_origin": "MIXED", + "result_basis": ["INFERRED"], + "runtime_maturity": "LOCAL", + "reason_code": null, + "qualifiers": [], + "warnings": ["1 branch node returned CANNOT_COMPLETE."], + "note": "2 branch nodes complete; 1 cannot complete" + }, + { + "stage_id": "clinical_simulation", + "result_status": "COMPLETE_WITH_WARNINGS", + "execution_status": "COMPLETE_WITH_WARNINGS", + "module_execution_status": "COMPLETE", + "output_origin": "MIXED", + "result_basis": ["MODELED"], + "runtime_maturity": "LOCAL", + "reason_code": null, + "qualifiers": [], + "warnings": ["1 branch node returned CANNOT_COMPLETE."], + "note": "2 branch nodes complete; 1 cannot complete" + }, + { + "stage_id": "roi_calculator", + "result_status": "COMPLETE_WITH_WARNINGS", + "execution_status": "COMPLETE_WITH_WARNINGS", + "module_execution_status": "COMPLETE", + "output_origin": "MIXED", + "result_basis": ["MODELED"], + "runtime_maturity": "LOCAL", + "reason_code": null, + "qualifiers": [], + "warnings": ["1 branch node returned CANNOT_COMPLETE."], + "note": "2 branch nodes complete; 1 cannot complete" + }, + { + "stage_id": "simulation", + "result_status": "COMPLETE", + "execution_status": "COMPLETE", + "module_execution_status": "COMPLETE", + "output_origin": "DETERMINISTIC_REPLAY", + "result_basis": ["OBSERVED", "MODELED"], + "runtime_maturity": "LOCAL", + "reason_code": null, + "qualifiers": [], + "warnings": [], + "note": "3 branch nodes complete" + } + ], + "branches": [ + { + "branch_id": "BR-01-b1", + "status": "COMPLETE", + "focus": { + "thing_id": "b1", + "name": "Marker one", + "kind": "biomarker", + "display_label": "Marker one", + "finding_ids": ["f1"], + "link_ids": ["l1"], + "support_count": 2, + "evidence_count": 2, + "mentions": 3 + }, + "nodes": { + "hypothesis_generator": { + "module_id": "hypothesis_generator", + "status": "COMPLETE", + "reason_code": null, + "message": null, + "output_origin": "DETERMINISTIC_REPLAY", + "input_ref": "branches/BR-01-b1/hypothesis_generator/input.json", + "input_hash": "sha256:input-hyp-b1", + "output_ref": "branches/BR-01-b1/hypothesis_generator/output.json", + "output_hash": "sha256:output-hyp-b1", + "duration_ms": 120, + "exit_code": 0, + "producer": {"repository": "REagent-LABrador/Hypothesis_Generator", "git_sha": "hyp-sha"}, + "artifact": { + "status": "COMPLETE", + "hypothesis": { + "schema_version": "2.0", + "hypothesis": { + "id": "H-b1", + "scores": {"support": 0.8, "novelty": 0.6, "testability": 0.7}, + "articulation": { + "statement": "Marker one predicts reduced synovial inflammation", + "mechanism": "IRAK4 inhibition changes the pathway", + "novel_because": "The evidence links this marker to an under-tested response axis." + } + } + }, + "cards": { + "hypotheses": [{"id": "H-b1", "scores": {"support": 0.8, "novelty": 0.6, "testability": 0.7}}], + "interpretability": {"schema_version": "1.0.0", "headline": {"title": "Focused hypothesis", "result": "H-b1", "plain_language": "One canonical branch hypothesis.", "status": "QUALIFIED", "basis": ["INFERRED"]}, "metrics": [], "steps": [], "evidence": [], "assumptions": [], "uncertainty": {"method": "producer score vector", "intervals": [], "limitations": []}, "limitations": [], "counterfactuals": [], "lineage": [], "extensions": {}} + } + } + }, + "clinical_simulation": { + "module_id": "clinical_simulation", + "status": "COMPLETE", + "reason_code": null, + "message": null, + "output_origin": "DETERMINISTIC_REPLAY", + "input_ref": "branches/BR-01-b1/clinical_simulation/input.json", + "input_hash": "sha256:input-clin-b1", + "output_ref": "branches/BR-01-b1/clinical_simulation/output.json", + "output_hash": "sha256:output-clin-b1", + "duration_ms": 80, + "exit_code": 0, + "producer": {"repository": "REagent-LABrador/clinical_simulation", "git_sha": "clinical-sha"}, + "artifact": {"status": "ok", "score": 0.72, "simulated_months_to_enroll": 22, "simulated_months_range": [18, 30], "screens_per_enrollee": 3, "why": "Enrollment remains feasible with timing risk."} + }, + "simulation": { + "module_id": "simulation", + "status": "COMPLETE", + "reason_code": null, + "message": null, + "output_origin": "DETERMINISTIC_REPLAY", + "input_ref": "branches/BR-01-b1/simulation/input.json", + "input_hash": "sha256:input-sim-b1", + "output_ref": "branches/BR-01-b1/simulation/output.json", + "output_hash": "sha256:output-sim-b1", + "duration_ms": 90, + "exit_code": 0, + "producer": {"repository": "REagent-LABrador/simulation", "git_sha": "simulation-sha"}, + "artifact": {"status": "ok", "verdict": "tractable", "verdict_basis": "retrieved_precedent", "target": {"uniprot_accession": "Q9NWZ3"}} + }, + "roi_calculator": { + "module_id": "roi_calculator", + "status": "COMPLETE", + "reason_code": null, + "message": null, + "output_origin": "DETERMINISTIC_REPLAY", + "input_ref": "branches/BR-01-b1/roi_calculator/input.json", + "input_hash": "sha256:input-roi-b1", + "output_ref": "branches/BR-01-b1/roi_calculator/output.json", + "output_hash": "sha256:output-roi-b1", + "duration_ms": 70, + "exit_code": 0, + "producer": {"repository": "REagent-LABrador/rnpv-roi-calculator", "git_sha": "roi-sha"}, + "artifact": {"status": "ok", "payload": {"summary": {"p10_rnpv": -10000000, "p50_rnpv": 45000000, "p90_rnpv": 110000000, "probability_positive_rnpv": 0.63}}} + } + } + }, + { + "branch_id": "BR-02-b2", + "status": "CANNOT_COMPLETE", + "focus": {"thing_id": "b2", "name": "Marker two", "kind": "biomarker", "display_label": "Marker two", "finding_ids": ["f2"], "link_ids": ["l2"], "support_count": 1, "evidence_count": 1, "mentions": 1}, + "nodes": { + "hypothesis_generator": { + "module_id": "hypothesis_generator", + "status": "CANNOT_COMPLETE", + "reason_code": "CREDENTIAL_MISSING", + "message": "ANTHROPIC_API_KEY is missing", + "output_origin": "NOT_RUN", + "input_ref": "branches/BR-02-b2/hypothesis_generator/input.json", + "input_hash": "sha256:input-hyp-b2", + "output_ref": "branches/BR-02-b2/hypothesis_generator/output.json", + "output_hash": "sha256:output-hyp-b2-failure", + "duration_ms": 1, + "exit_code": 2, + "producer": {"repository": "REagent-LABrador/Hypothesis_Generator", "git_sha": "hyp-sha"}, + "artifact": {"status": "CANNOT_COMPLETE", "reason_code": "CREDENTIAL_MISSING", "message": "ANTHROPIC_API_KEY is missing"} + }, + "clinical_simulation": { + "module_id": "clinical_simulation", + "status": "CANNOT_COMPLETE", + "reason_code": "UPSTREAM_FAILED", + "message": "required upstream node hypothesis_generator did not complete", + "output_origin": "NOT_RUN", + "input_ref": "branches/BR-02-b2/clinical_simulation/input.json", + "input_hash": "sha256:input-clin-b2", + "output_ref": "branches/BR-02-b2/clinical_simulation/output.json", + "output_hash": "sha256:output-clin-b2-failure", + "duration_ms": 0, + "exit_code": null, + "producer": {"repository": "REagent-LABrador/clinical_simulation", "git_sha": "clinical-sha"}, + "artifact": {"status": "CANNOT_COMPLETE", "reason_code": "UPSTREAM_FAILED", "message": "required upstream node hypothesis_generator did not complete"} + }, + "simulation": { + "module_id": "simulation", + "status": "COMPLETE", + "reason_code": null, + "message": null, + "output_origin": "DETERMINISTIC_REPLAY", + "input_ref": "branches/BR-02-b2/simulation/input.json", + "input_hash": "sha256:input-sim-b2", + "output_ref": "branches/BR-02-b2/simulation/output.json", + "output_hash": "sha256:output-sim-b2", + "duration_ms": 92, + "exit_code": 0, + "producer": {"repository": "REagent-LABrador/simulation", "git_sha": "simulation-sha"}, + "artifact": {"status": "ok", "verdict": "tractable", "target": {"uniprot_accession": "Q9NWZ3"}} + }, + "roi_calculator": { + "module_id": "roi_calculator", + "status": "CANNOT_COMPLETE", + "reason_code": "UPSTREAM_FAILED", + "message": "required upstream node hypothesis_generator did not complete", + "output_origin": "NOT_RUN", + "input_ref": "branches/BR-02-b2/roi_calculator/input.json", + "input_hash": "sha256:input-roi-b2", + "output_ref": "branches/BR-02-b2/roi_calculator/output.json", + "output_hash": "sha256:output-roi-b2-failure", + "duration_ms": 0, + "exit_code": null, + "producer": {"repository": "REagent-LABrador/rnpv-roi-calculator", "git_sha": "roi-sha"}, + "artifact": {"status": "CANNOT_COMPLETE", "reason_code": "UPSTREAM_FAILED", "message": "required upstream node hypothesis_generator did not complete"} + } + } + }, + { + "branch_id": "BR-03-p1", + "status": "COMPLETE", + "focus": {"thing_id": "p1", "name": "Pathway readout", "kind": "process", "display_label": "Mechanistic/PD readout: Pathway readout", "finding_ids": ["f1", "f2"], "link_ids": ["l1", "l2"], "support_count": 2, "evidence_count": 2, "mentions": 4}, + "nodes": { + "hypothesis_generator": { + "module_id": "hypothesis_generator", "status": "COMPLETE", "reason_code": null, "message": null, "output_origin": "DETERMINISTIC_REPLAY", "input_ref": "branches/BR-03-p1/hypothesis_generator/input.json", "input_hash": "sha256:input-hyp-p1", "output_ref": "branches/BR-03-p1/hypothesis_generator/output.json", "output_hash": "sha256:output-hyp-p1", "duration_ms": 125, "exit_code": 0, "producer": {"repository": "REagent-LABrador/Hypothesis_Generator", "git_sha": "hyp-sha"}, + "artifact": {"status": "COMPLETE", "hypothesis": {"schema_version": "2.0", "hypothesis": {"id": "H-p1", "scores": {"support": 0.7, "novelty": 0.8, "testability": 0.65}, "articulation": {"statement": "Pathway readout predicts pharmacodynamic response", "mechanism": "The readout tracks IRAK4 pathway suppression", "novel_because": "It is a supported mechanistic readout rather than a fallback biomarker."}}}, "cards": {"hypotheses": [{"id": "H-p1", "scores": {"support": 0.7, "novelty": 0.8, "testability": 0.65}}]}} + }, + "clinical_simulation": { + "module_id": "clinical_simulation", "status": "COMPLETE", "reason_code": null, "message": null, "output_origin": "DETERMINISTIC_REPLAY", "input_ref": "branches/BR-03-p1/clinical_simulation/input.json", "input_hash": "sha256:input-clin-p1", "output_ref": "branches/BR-03-p1/clinical_simulation/output.json", "output_hash": "sha256:output-clin-p1", "duration_ms": 82, "exit_code": 0, "producer": {"repository": "REagent-LABrador/clinical_simulation", "git_sha": "clinical-sha"}, + "artifact": {"status": "ok", "score": 0.81, "simulated_months_to_enroll": 19, "simulated_months_range": [16, 26], "screens_per_enrollee": 2.5, "why": "The readout is compatible with the proposed enrollment frame."} + }, + "simulation": { + "module_id": "simulation", "status": "COMPLETE", "reason_code": null, "message": null, "output_origin": "DETERMINISTIC_REPLAY", "input_ref": "branches/BR-03-p1/simulation/input.json", "input_hash": "sha256:input-sim-p1", "output_ref": "branches/BR-03-p1/simulation/output.json", "output_hash": "sha256:output-sim-p1", "duration_ms": 95, "exit_code": 0, "producer": {"repository": "REagent-LABrador/simulation", "git_sha": "simulation-sha"}, + "artifact": {"status": "ok", "verdict": "tractable", "verdict_basis": "both", "target": {"uniprot_accession": "Q9NWZ3"}, "next_experiment": {"description": "Measure target engagement in synovial tissue."}} + }, + "roi_calculator": { + "module_id": "roi_calculator", "status": "COMPLETE", "reason_code": null, "message": null, "output_origin": "DETERMINISTIC_REPLAY", "input_ref": "branches/BR-03-p1/roi_calculator/input.json", "input_hash": "sha256:input-roi-p1", "output_ref": "branches/BR-03-p1/roi_calculator/output.json", "output_hash": "sha256:output-roi-p1", "duration_ms": 72, "exit_code": 0, "producer": {"repository": "REagent-LABrador/rnpv-roi-calculator", "git_sha": "roi-sha"}, + "artifact": {"status": "ok", "payload": {"summary": {"p10_rnpv": -5000000, "p50_rnpv": 30000000, "p90_rnpv": 90000000, "probability_positive_rnpv": 0.71}}} + } + } + } + ], + "highlander_ready": true, + "highlander": { + "launched": true, + "job_id": "HL-SCIENTIFIC-TEST", + "packet_snapshot": {"id": "PKT-SCIENTIFIC-TEST-R42"}, + "result_hash": "sha256:highlander-result-test", + "result": { + "schemaVersion": "highlander.packet-comparison-result.v1", + "objectivePolicyId": "program-frontier.v1", + "snapshotId": "HL-SNAPSHOT-TEST", + "createdAt": "2026-08-16T18:00:01Z", + "runId": "LR-SCIENTIFIC-TEST", + "objectivePolicy": {"policyId": "program-frontier.v1", "objectives": [{"objectiveId": "hypothesis.support", "direction": "MAXIMIZE"}, {"objectiveId": "hypothesis.novelty", "direction": "MAXIMIZE"}]}, + "candidates": [ + {"candidateId": "H-b1", "packetRevisionId": "packet-b1", "packetHash": "sha256:packet-b1", "comparisonStatus": "FRONTIER", "observations": [{"objectiveId": "hypothesis.support", "rawValue": 0.8, "direction": "MAXIMIZE", "unit": "fraction", "evidenceBasis": "INFERRED"}, {"objectiveId": "hypothesis.novelty", "rawValue": 0.6, "direction": "MAXIMIZE", "unit": "fraction", "evidenceBasis": "INFERRED"}], "qualifiers": ["NOMINAL_PARETO"]}, + {"candidateId": "BR-02-b2", "packetRevisionId": "packet-b2", "packetHash": "sha256:packet-b2", "comparisonStatus": "INCOMPARABLE", "observations": [], "qualifiers": ["INCOMPLETE_BRANCH"], "incomparableReasons": ["HYPOTHESIS_GENERATOR:CREDENTIAL_MISSING"]}, + {"candidateId": "H-p1", "packetRevisionId": "packet-p1", "packetHash": "sha256:packet-p1", "comparisonStatus": "FRONTIER", "observations": [{"objectiveId": "hypothesis.support", "rawValue": 0.7, "direction": "MAXIMIZE", "unit": "fraction", "evidenceBasis": "INFERRED"}, {"objectiveId": "hypothesis.novelty", "rawValue": 0.8, "direction": "MAXIMIZE", "unit": "fraction", "evidenceBasis": "INFERRED"}], "qualifiers": ["NOMINAL_PARETO"]} + ], + "comparisonGroups": [], + "frontier": ["H-b1", "H-p1"], + "dominated": [], + "incomparable": [{"candidateId": "BR-02-b2", "reasons": ["HYPOTHESIS_GENERATOR:CREDENTIAL_MISSING"]}], + "dominanceRelationships": [], + "equivalenceGroups": [], + "qualifiers": ["NO_GLOBAL_WINNER", "NOMINAL_PARETO"], + "nextEvidenceAction": {"actionId": "action-test", "actionType": "run_follow_up", "target": "Q9NWZ3", "description": "Measure target engagement in synovial tissue.", "producerModuleId": "small-molecule-tractability-review", "producerOutputSha256": "sha256:output-sim-p1", "sourceId": "next_experiment", "sourcePath": "$.next_experiment", "candidateIds": ["H-p1"], "candidateCount": 1, "selectionBasis": "PRODUCER_EMITTED_MOST_BRANCHES_STABLE_TIEBREAK"} + } + }, + "modules": [], + "warnings": [], + "errors": [{"code": "CREDENTIAL_MISSING", "module": "hypothesis_generator", "branch": "BR-02-b2", "message": "ANTHROPIC_API_KEY is missing"}] +} diff --git a/tests/frontend-live-contract.test.mjs b/tests/frontend-live-contract.test.mjs index ccecf19..91d7d08 100644 --- a/tests/frontend-live-contract.test.mjs +++ b/tests/frontend-live-contract.test.mjs @@ -10,6 +10,12 @@ const terminalSnapshot = JSON.parse( "utf8", ), ); +const scientificSnapshot = JSON.parse( + fs.readFileSync( + new URL("./fixtures/orchestrator-scientific-snapshot.json", import.meta.url), + "utf8", + ), +); const appHtml = fs.readFileSync(new URL("../app/index.html", import.meta.url), "utf8"); function ingestTerminalSnapshot() { @@ -19,6 +25,20 @@ function ingestTerminalSnapshot() { return harness; } +function ingestScientificSnapshot(snapshot = scientificSnapshot) { + const harness = loadFunctionalApp({ search: "?backend=http&mode=scientific" }); + prepareRun(harness); + harness.hooks.state.snapshot = Object.freeze({ + ...harness.hooks.state.snapshot, + biomarkers: 3, + hypotheses: 1, + }); + harness.hooks.state.runData.requestedLanes = 3; + harness.hooks.buildScaffold(); + harness.hooks.ingestSnapshot(structuredClone(snapshot)); + return harness; +} + test("stage metrics are exposed as visible button groups instead of selects", () => { assert.doesNotMatch(appHtml, /]+data-metric-stage=/); for (const [stage, expectedCount] of [ @@ -516,3 +536,173 @@ test("frontend v0 rejects unsupported indications before creating a run", () => const supported = hooks.validateSetup(); assert.equal(supported.valid, true); }); + +test("scientific snapshot binds three exact focus branches and native artifacts", () => { + const { hooks } = ingestScientificSnapshot(); + + assert.equal(hooks.state.scientificSnapshot, true); + assert.equal(hooks.state.runData.biomarkers.length, 3); + assert.equal(hooks.state.runData.programs.length, 3); + assert.deepEqual( + hooks.state.runData.programs.map((program) => program.id), + ["BR-01-b1", "BR-02-b2", "BR-03-p1"], + ); + assert.equal( + hooks.state.runData.biomarkers[2].label, + "Mechanistic/PD readout: Pathway readout", + ); + assert.equal(hooks.state.runData.programs[0].metrics.recruit, 72); + assert.equal(hooks.state.runData.programs[0].metrics.duration, 22); + assert.equal(hooks.state.runData.programs[0].metrics.rnpv, 45); + assert.equal(hooks.state.runData.programs[0].stationPayloads.recruitability.simulated_months_to_enroll, 22); + assert.equal(hooks.state.runData.programs[0].scientificNodes.roi_calculator.output_hash, "sha256:output-roi-b1"); +}); + +test("scientific inspector shows exact hashes, native simulated names, and terminal reasons", () => { + const { hooks } = ingestScientificSnapshot(); + + hooks.renderInspector(hooks.findNode("recruitability-slot-0")); + const clinicalHtml = hooks.elements.inspectorBody.innerHTML; + assert.match(clinicalHtml, /simulated_months_to_enroll/); + assert.match(clinicalHtml, /sha256:input-clin-b1/); + assert.match(clinicalHtml, /sha256:output-clin-b1/); + assert.match(clinicalHtml, /REagent-LABrador\/clinical_simulation/); + + hooks.renderInspector(hooks.findNode("hyp-slot-1")); + const failureHtml = hooks.elements.inspectorBody.innerHTML; + assert.match(failureHtml, /CREDENTIAL_MISSING/); + assert.match(failureHtml, /ANTHROPIC_API_KEY is missing/); + assert.match(failureHtml, /CANNOT_COMPLETE/); + assert.match(failureHtml, /NOT RUN/); + assert.match(failureHtml, /sha256:output-hyp-b2-failure/); +}); + +test("scientific comparison membership comes only from the server result", () => { + const { hooks } = ingestScientificSnapshot(); + const frontier = hooks.state.runData.programs[0]; + const processFrontier = hooks.state.runData.programs[2]; + const failed = hooks.state.runData.programs[1]; + + assert.equal(hooks.serverStatusLabel(frontier), "FRONTIER"); + assert.equal(hooks.serverStatusLabel(processFrontier), "FRONTIER"); + assert.equal(hooks.serverStatusLabel(failed), "INCOMPARABLE"); + assert.equal(hooks.programStatus(frontier), "non-dominated"); + + frontier.metrics = { + ...frontier.metrics, + rnpv: -999999, + recruit: 0, + tractability_fit: 0, + }; + processFrontier.metrics = { + ...processFrontier.metrics, + rnpv: 999999, + recruit: 100, + tractability_fit: 100, + }; + frontier.displayMetricBasis = "REPRESENTATIVE_DEMO_SCENARIO_V1"; + frontier.displayMetrics = { rnpv: -1, recruit: -1, tractability_fit: -1 }; + + assert.equal( + hooks.programStatus(frontier), + "non-dominated", + "browser and representative values cannot change server Pareto membership", + ); + assert.equal(hooks.programStatus(processFrontier), "non-dominated"); +}); + +test("server Highlander Pareto set and producer-grounded action render verbatim", () => { + const { hooks } = ingestScientificSnapshot(); + hooks.renderHighlander(); + + const html = hooks.elements.serverHighlanderResult.innerHTML; + assert.equal(hooks.elements.serverHighlanderResult.hidden, false); + assert.match(html, /H-b1, H-p1/); + assert.match(html, /Measure target engagement in synovial tissue/); + assert.match(html, /sha256:output-sim-p1/); + assert.match(html, /sha256:highlander-result-test/); + assert.match(hooks.elements.programDetail.innerHTML, /SERVER|FRONTIER/); + assert.match(hooks.elements.programDetail.innerHTML, /Representative values excluded from scientific packet: yes/); +}); + +test("representative watermark appears only for explicit representative demo mode", () => { + const scientific = ingestScientificSnapshot(); + assert.equal(scientific.hooks.elements.representativeWatermark.hidden, true); + assert.equal(scientific.hooks.elements.representativeWatermark.textContent, ""); + + const strayWatermark = structuredClone(scientificSnapshot); + strayWatermark.watermark = "SHOULD NOT SHOW"; + const nonRepresentative = ingestScientificSnapshot(strayWatermark); + assert.equal(nonRepresentative.hooks.elements.representativeWatermark.hidden, true); + + const representative = structuredClone(scientificSnapshot); + representative.presentation_mode = "REPRESENTATIVE_DEMO"; + representative.representative_demo = true; + representative.watermark = "REPRESENTATIVE DEMO VALUES"; + const demo = ingestScientificSnapshot(representative); + assert.equal(demo.hooks.elements.representativeWatermark.hidden, false); + assert.equal(demo.hooks.elements.representativeWatermark.textContent, "REPRESENTATIVE DEMO VALUES"); +}); + +test("explicit scientific launch mode builds the checked-in v3 IRAK4 replay frame", () => { + const { hooks, document } = loadFunctionalApp({ search: "?backend=http&mode=scientific" }); + hooks.applyBootMode(); + const setup = hooks.buildScientificSetup({ + indication: "Rheumatoid arthritis", + biomarkers: 3, + }); + + assert.equal(hooks.BOOT.launchMode, "scientific"); + assert.equal(setup.schemaVersion, "labrador.run-setup.v3"); + assert.equal(setup.execution.mode, "REPLAY"); + assert.equal(setup.execution.presentationMode, "SCIENTIFIC"); + assert.equal(setup.exploration.focus.maxBranches, 3); + assert.equal(setup.program.frame.target.symbol, "IRAK4"); + assert.equal(setup.program.frame.target.uniprotAccession, "Q9NWZ3"); + assert.equal(setup.program.valuationFrame.target, "IRAK4"); + assert.equal(hooks.elements.maxHypotheses.value, "1"); + assert.equal(hooks.elements.maxHypotheses.disabled, true); + assert.match(document.getElementById("setup-mode-chip").textContent, /Scientific replay/); +}); + +test("run query attaches read-only without creating another backend run", () => { + let attachedRunId = null; + let createCalls = 0; + const httpBackend = { + createRun() { + createCalls += 1; + return Promise.resolve({ runId: "LR-UNEXPECTED" }); + }, + startPolling(runId) { + attachedRunId = runId; + return { stop() {} }; + }, + }; + const { hooks, document } = loadFunctionalApp({ + search: "?backend=http&mode=scientific&run=LR-SCIENTIFIC-TEST", + httpBackend, + }); + const setNumber = (id, value, min, max) => { + const input = document.getElementById(id); + input.value = String(value); + input.min = String(min); + input.max = String(max); + }; + document.getElementById("clinical-indication").value = "Rheumatoid arthritis"; + setNumber("max-biomarkers", 3, 1, 10); + setNumber("max-papers", 40, 1, 100); + setNumber("max-hypotheses", 1, 1, 10); + setNumber("biomarker-low", 1, 1, 10); + setNumber("biomarker-high", 10, 1, 10); + setNumber("hypothesis-low", 1, 1, 10); + setNumber("hypothesis-high", 10, 1, 10); + + hooks.applyBootMode(); + hooks.attachConfiguredRun(hooks.BOOT.runId); + + assert.equal(hooks.BOOT.runId, "LR-SCIENTIFIC-TEST"); + assert.equal(attachedRunId, "LR-SCIENTIFIC-TEST"); + assert.equal(createCalls, 0); + assert.equal(hooks.state.runId, "LR-SCIENTIFIC-TEST"); + assert.match(document.getElementById("snapshot-note").textContent, /No new run was created/); +}); diff --git a/tests/helpers/load-functional-app.mjs b/tests/helpers/load-functional-app.mjs index 1cee9f2..05efe9b 100644 --- a/tests/helpers/load-functional-app.mjs +++ b/tests/helpers/load-functional-app.mjs @@ -3,8 +3,15 @@ import vm from "node:vm"; import { interpretabilityView, + isScientificSnapshot, normalizeStageTruth, resolveBackendBase, + scientificCandidateId, + scientificComparisonStatus, + scientificHighlanderCandidate, + scientificModuleId, + scientificNodeForStage, + scientificStageId, stationPayloadFor, } from "../../app/js/snapshot-contract.js"; @@ -17,6 +24,7 @@ export class FakeElement { this.dataset = {}; this.disabled = false; this.innerHTML = ""; + this.hidden = false; this.max = ""; this.min = ""; this.open = false; @@ -64,7 +72,7 @@ export class FakeElement { } } -export function loadFunctionalApp({ search = "?backend=http" } = {}) { +export function loadFunctionalApp({ search = "?backend=http", httpBackend = null } = {}) { const elements = new Map(); const getElement = (id) => { if (!elements.has(id)) elements.set(id, new FakeElement()); @@ -93,12 +101,19 @@ export function loadFunctionalApp({ search = "?backend=http" } = {}) { clearTimeout, console, createHttpBackend() { - return {}; + return httpBackend || {}; }, document, interpretabilityView, + isScientificSnapshot, normalizeStageTruth, resolveBackendBase, + scientificCandidateId, + scientificComparisonStatus, + scientificHighlanderCandidate, + scientificModuleId, + scientificNodeForStage, + scientificStageId, stationPayloadFor, setTimeout, window, @@ -111,8 +126,12 @@ export function loadFunctionalApp({ search = "?backend=http" } = {}) { const hookInjection = ` globalThis.__LABRADOR_TEST_HOOKS__ = { state, + BOOT, elements, applyStationDerivations, + applyBootMode, + attachConfiguredRun, + buildScientificSetup, buildScaffold, bindStage, centerGraphOnActiveLineage, @@ -120,7 +139,11 @@ export function loadFunctionalApp({ search = "?backend=http" } = {}) { ingestSnapshot, metricCell, programStatus, + renderHighlander, renderInspector, + renderServerHighlanderResult, + serverStatusLabel, + translateScientificWire, translateWire, validateSetup, renderInterpretability: diff --git a/verify_functional_app.mjs b/verify_functional_app.mjs index 23a8253..5dfd3ad 100644 --- a/verify_functional_app.mjs +++ b/verify_functional_app.mjs @@ -6,8 +6,12 @@ import { fileURLToPath } from "node:url"; import { interpretabilityView, + isScientificSnapshot, normalizeStageTruth, resolveBackendBase, + scientificCandidateId, + scientificComparisonStatus, + scientificNodeForStage, stationPayloadFor, } from "./app/js/snapshot-contract.js"; @@ -103,6 +107,24 @@ assert.equal( "http://127.0.0.1:8787", "an integrated one-process run must use the serving origin", ); + +const scientificFixture = JSON.parse( + readFileSync( + new URL("./tests/fixtures/orchestrator-scientific-snapshot.json", import.meta.url), + "utf8", + ), +); +assert.equal(isScientificSnapshot(scientificFixture), true); +assert.equal(scientificCandidateId(scientificFixture.branches[0]), "H-b1"); +assert.equal( + scientificNodeForStage(scientificFixture.branches[0], "recruitability").artifact + .simulated_months_to_enroll, + 22, +); +assert.equal( + scientificComparisonStatus(scientificFixture.highlander.result, "H-p1"), + "FRONTIER", +); assert.equal( resolveBackendBase("?base=http%3A%2F%2Flocalhost%3A9999", "http://127.0.0.1:8787"), "http://localhost:9999", @@ -129,6 +151,9 @@ for (const required of [ "stationPayloadFor", "interpretabilityView", "HIGHLANDER CLIENT-SIDE · SERVER CONSUMER NOT WIRED", + "labrador.run-setup.v3", + "Run server Highlander", + "scientific_packet_excludes_representative_values", ]) { assert.ok(app.includes(required), `functional app must wire ${required}`); } @@ -183,3 +208,4 @@ console.log(" Payloads: biomarker singular and program stage maps are consumed. console.log(" Interpretability: readable projection retains native JSON verbatim."); console.log(" Backend base: integrated serving defaults to same origin."); console.log(" Highlander: returned plans map to the ROI × recruitability × simulation 3D view; the chart fills its panel."); +console.log(" Scientific v1: native branches and server Highlander results stay separate from representative display values."); From 95c9de4f3871f14108b3a595579cd991fc26c7ef Mon Sep 17 00:00:00 2001 From: Vince Alessi Date: Sun, 16 Aug 2026 05:52:38 -0700 Subject: [PATCH 2/3] fix: align scientific replay preset --- app/js/app.js | 5 +++-- tests/frontend-live-contract.test.mjs | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/js/app.js b/app/js/app.js index 92a1f3d..05d8cd1 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -345,8 +345,9 @@ exploration: { evidenceRequest: { ask: "new_question", - target: "Can IRAK4 inhibition suppress synovial inflammation in " + validation.indication + "?", - depth: "deep" + target: "can a small-molecule IRAK4 inhibitor suppress synovial fibroblast-driven inflammation in rheumatoid arthritis, or is its effect confined to the myeloid compartment?", + depth: "deep", + reason: "frozen golden path for the REagent-LABrador integration demo" }, focus: { maxBranches: validation.biomarkers }, hypothesis: { diff --git a/tests/frontend-live-contract.test.mjs b/tests/frontend-live-contract.test.mjs index 91d7d08..6ce10c6 100644 --- a/tests/frontend-live-contract.test.mjs +++ b/tests/frontend-live-contract.test.mjs @@ -656,6 +656,14 @@ test("explicit scientific launch mode builds the checked-in v3 IRAK4 replay fram assert.equal(setup.schemaVersion, "labrador.run-setup.v3"); assert.equal(setup.execution.mode, "REPLAY"); assert.equal(setup.execution.presentationMode, "SCIENTIFIC"); + assert.equal( + setup.exploration.evidenceRequest.target, + "can a small-molecule IRAK4 inhibitor suppress synovial fibroblast-driven inflammation in rheumatoid arthritis, or is its effect confined to the myeloid compartment?", + ); + assert.equal( + setup.exploration.evidenceRequest.reason, + "frozen golden path for the REagent-LABrador integration demo", + ); assert.equal(setup.exploration.focus.maxBranches, 3); assert.equal(setup.program.frame.target.symbol, "IRAK4"); assert.equal(setup.program.frame.target.uniprotAccession, "Q9NWZ3"); From a9a9c7d888f486ff11fc566a2baad75c4fd8c97c Mon Sep 17 00:00:00 2001 From: Vince Alessi Date: Sun, 16 Aug 2026 06:22:23 -0700 Subject: [PATCH 3/3] Fix replay focus truth label --- app/js/app.js | 2 +- tests/frontend-live-contract.test.mjs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/js/app.js b/app/js/app.js index 05d8cd1..ab56752 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -1462,7 +1462,7 @@ slot: index, id: "bio-slot-" + index, label: focus.display_label || focus.name || focus.thing_id || branch.branch_id, - summary: (focus.kind === "process" ? "Mechanistic/PD readout focus" : "Biomarker focus") + " selected from live evidence; " + (focus.support_count || 0) + " supporting finding(s).", + summary: (focus.kind === "process" ? "Mechanistic/PD readout focus" : "Biomarker focus") + " selected from producer evidence; " + (focus.support_count || 0) + " supporting finding(s).", metrics: { exploration: null, evidence: null, pursuit: null }, nativeMetrics: { exploration: null, evidence: null, pursuit: null }, uncertainty: "Focus selection preserves mapper finding and link IDs; no display score is imputed.", diff --git a/tests/frontend-live-contract.test.mjs b/tests/frontend-live-contract.test.mjs index 6ce10c6..3a1a9c0 100644 --- a/tests/frontend-live-contract.test.mjs +++ b/tests/frontend-live-contract.test.mjs @@ -558,6 +558,29 @@ test("scientific snapshot binds three exact focus branches and native artifacts" assert.equal(hooks.state.runData.programs[0].scientificNodes.roi_calculator.output_hash, "sha256:output-roi-b1"); }); +test("scientific focus copy stays mode-neutral while explicit LIVE origins remain visible", () => { + const replay = ingestScientificSnapshot(); + const processFocus = replay.hooks.state.runData.biomarkers[2]; + + assert.equal(replay.hooks.state.executionMode, "REPLAY"); + assert.match(processFocus.summary, /selected from producer evidence/); + assert.doesNotMatch(processFocus.summary, /selected from live evidence/i); + assert.equal(replay.hooks.findNode("hyp-slot-2").outputOrigin, "DETERMINISTIC_REPLAY"); + + const liveSnapshot = structuredClone(scientificSnapshot); + liveSnapshot.execution_mode = "LIVE"; + liveSnapshot.branches[2].nodes.hypothesis_generator.output_origin = "LIVE"; + const live = ingestScientificSnapshot(liveSnapshot); + live.hooks.renderInspector(live.hooks.findNode("hyp-slot-2")); + + assert.equal(live.hooks.state.executionMode, "LIVE"); + assert.match(live.hooks.state.runData.biomarkers[2].summary, /selected from producer evidence/); + assert.match( + live.hooks.elements.inspectorBody.innerHTML, + /Output origin<\/span>LIVE<\/strong>/, + ); +}); + test("scientific inspector shows exact hashes, native simulated names, and terminal reasons", () => { const { hooks } = ingestScientificSnapshot();