Skip to content

Technical documentationInternal

For the team and anyone taking the product over. Not linked from the site and not indexed.

Technical documentation pages

Science overview

How Drybench tries to make its numbers scientifically correct and keep them so: the rules the code enforces across every app, the shared scientific libraries in src/lib/, how numbers are rounded for display, the audits and the evidence they left, who controls scientific claims, and an index of each app's methods. Per-app science is on the app pages; this page is the cross-cutting layer and the place to start a scientific review.

Scope and how to read this page

This page is written for a scientific due-diligence reader. It covers:

  • the principles the code enforces on every number (provenance, sources and licences, stated limits, the "Unverified" label, verification states, units);
  • the shared libraries every app builds on, with their equations, numerical methods, tolerances and tests;
  • display formatting and rounding;
  • the three scientific audit records of 7 to 10 October 2026, what they found and what is still open;
  • the verification evidence in public/verification/ and docs/science/evidence/;
  • the Chief Scientific Officer seat and how scientific copy is controlled;
  • an index of every app's methods and the open scientific risks.

What it does not cover in depth: each app's own engine (see the app pages), the third-party engines (see Engines), and how each fixture is generated and compared (see Oracles and verification). Data licences are on Data sources.

Two cautions apply throughout.

  • Every audit and every "CSO" ruling cited here was produced by AI-assisted review. The CSO seat is held by an agent (.claude/agents/cso.md). No named human scientific reviewer has signed off any app; that is open question Q-19 in docs/board/open-questions.md.
  • "Verified" in this codebase has a narrow meaning: a method's output matched a named external oracle on published fixtures within a stated tolerance (content/apps/assay-analyser.methods.ts, comment on METHODS). It does not mean the model is a correct description of biology, and it is not regulatory validation. The audits say so repeatedly: "Passing a golden fixture establishes agreement with its oracle or stored output. It does not establish that the oracle, parameter set, uncertainty model or biological interpretation is correct" (docs/science/2026-10-scientific-audit.md, review record).

How correctness is achieved and maintained

The mechanisms, in the order a number meets them:

Stage Mechanism Where
Before code A method is written from its primary publication, with the citation and the date it was read in the module header; no GPL code is ported (GPL tools are used offline as oracles only) Module headers in src/lib/; registry oracles[] entries
Engine Pure TypeScript engines with explicit refusals for unsupported inputs; numerical policies (tolerances, search limits) are constants with comments src/lib/fit, src/lib/seq, src/lib/dilution, src/lib/units, src/lib/hosts
Engine tests Node unit tests, including a "permanent science regression suite" of invariants the 7 October audit asked for src/lib/*/science.test.ts, src/lib/hosts/science.test.ts
Independent oracle Fixtures written by another implementation (R nls/drc, SciPy, Biopython, Python RDKit, primer3-py, Python fractions, hand derivations) and compared by tests within stated tolerances pipeline/<slug>/, public/verification/<slug>/<version>/; see Oracles and verification
Immutability Published fixtures, goldens and manifests may not change once on main; changes need a new version or a dated supersession scripts/check-write-once.mjs, CI job check-write-once; see Data pipeline
Display Values stored at full precision, rounded once at display src/components/suite/form/format.ts
Provenance Method records (MethodRef) with implementation, version, reference and verification state; export provenance blocks; limits src/components/suite/data/provenance.ts, content/apps/*.methods.ts
Copy Forbidden regulatory phrases fail npm test; scientific copy is reviewed by the CSO seat content/apps/regulatory.ts, scripts/forbidden-phrases.mjs, content/apps.test.ts
Review Dated scientific audits, kept immutable, with implementation and follow-up records docs/science/

The roadmap (D-0041, docs/roadmap.md) states the rule the mechanisms serve: "Every new method ships with an independent reference fixture and a methods entry, or it does not ship" (rule 1). The 10 October audit found that rule broken for a number of methods; see Open scientific risks.

Principles the code enforces

Provenance

The provenance model is in src/components/suite/data/provenance.ts (contracts §5.5 and §6.1 in docs/design/shared-contracts.md).

  • SourceRef: a registry source id (a chip cannot name an unlisted source), version, data-as-of, retrieved and processed times, state (live, cached, snapshot, shipped, saved, computed), the exact query and a snapshot expiry.
  • MethodRef: id, name, implementation (file and version), version, an optional reference (citation, DOI, PMID), verification ("verified" | "unit-tested" | "unverified"), an optional verificationNote, settingsHash and computedAt.
  • ProvenanceBlock (schema version 1): what every export carries: suite, app slug, name, version and build hash; time and environment; subject and query; sources with licence id, licence page and the date it was read; methods with their verification state; the limits; the statements; and a fixed numbers record: { stored: "full-precision", rounding: "half-away-from-zero", locale: "en", dates: "ISO 8601, UTC" }.

Each library module keeps its method records in a light module with no engine code (src/lib/fit/methods.ts, src/lib/seq/methods-*.ts, src/lib/table/methods.ts, src/lib/hosts/methods.ts), so a chip or a methods page can cite a method without loading the engine. The app's content/apps/<slug>.methods.ts (or content/apps/sequence-toolkit/methods/) joins each record with its verification state and the sentence the fixtures support.

What the code does not yet do: provenance is attached per panel, chart, table and method, not to every individual number. Q-16 ("Provenance on every number, not every panel", owner CSO) is open, and the CSO agent's own instructions say "Until provenance reaches every number, the accurate wording is 'sources and limits on every panel'" (.claude/agents/cso.md). Contracts §6.1 calls for a test that "enumerates every value-bearing component and asserts a Provenance declaration"; no such test exists in the repository (the provenance tests in src/components/suite/data/provenance.test.ts cover the block builder, attribution lines and source joins only).

Sources and licences per panel

Every source an app uses is declared in its registry entry with a sourceMeta record: licence id, the date the licence page was read, and flags for share-alike, display-only and notices; excluded sources are listed with a reason. scripts/licences.mjs fails on a missing record or a licence read more than twelve months earlier. Licence ids include cited-method and cited-data for equations and constants taken from papers (the atomic weights, the nearest-neighbour tables). Details are on Registry and content and Data sources. Legal conclusions on any licence are held for the General Counsel (D-0010); this page records only the facts.

Known gaps: Q-26 (open) records that the literature workspace contacts Europe PMC, OpenAlex and Anthropic without registry entries, and that ChEMBL-derived compound names (CC BY-SA) reach several surfaces without the share-alike notice or a sourceMeta entry. The nearest-neighbour hairpin terminal-mismatch table from the NNDB carries the licence line "To be checked" (src/lib/seq/structure-params.ts header).

Stated limits and refusals

The engines refuse rather than guess. The pattern is consistent across libraries: an input outside the method's supported domain returns a typed refusal with a reason, never a clamped or default value. Examples, each checked in code:

Library Refusal File
Fit Numerically flat responses, unidentifiable midpoint or Hill, non-converged solver, nonpositive responses under relative weighting, fewer than four concentrations, a concentration of zero or below used as a point src/lib/fit/four-pl.ts, src/lib/fit/rules.ts
Tm Na⁺/K⁺/Tris above 2,000 mM, Mg²⁺ above 1,000 mM, dNTPs above 100 mM, oligo outside 1 pM to 1 mM, monovalent below 1 mM with free Mg²⁺ below 0.1 mM; any character other than A, C, G or T src/lib/seq/tm.ts, src/lib/seq/tm-bounds.ts
Genetic codes Stop-dependent translation and ORF calling under NCBI tables 27, 28 and 31 (dual-use codons) src/lib/seq/codes.ts, src/lib/seq/orf.ts
Recipe Stocks whose combined volume exceeds the final volume; purity outside (0, 1]; nonpositive or non-finite stocks src/lib/dilution/recipe.ts
Formula Elements with no standard atomic weight; X0 counts; unclosed brackets src/lib/units/formula-mass.ts
Calibration Fewer than 3 (linear) or 5 (4PL) standard concentrations; unknowns outside the standards' range src/lib/fit/calibration.ts
Bioreactor Yields that would create carbon or electrons src/lib/hosts/ (see Bioreactor)

Degenerate inputs: one relative bound. Whether a group has no spread, two groups have no separation, or a difference that must be positive is only rounding is decided by one helper, src/lib/stats/degenerate.ts (the CSO's audit-fix memo of 20 October 2026, item 1 (a)): tau(n, M) = 2·n·ε·M, M the largest |x| in the comparison, or the largest operand a derived value (a ΔCq, a blank-corrected reading) was computed from, passed as scale. noSpread(x) is max − min ≤ tau(n, M); noSeparation(a, b) is |mean(a) − mean(b)| ≤ tau(na + nb, M); notAbove(d, n, M) is d ≤ tau(n, M); zeroResidual(rss, Σy²) is rss ≤ 1e−20·Σy², for iterative fits only. Every test refuses on NaN. The bound is relative, so a real 10⁻¹⁴ M separation calculates where an absolute floor such as 64·ε·max(1, |x|) would refuse it; it refuses three readings of 0.1 against two, whose double means differ by 1.4 × 10⁻¹⁷. The factor 2 and the 1e−20 are the CSO's judgements. Tested in src/lib/stats/degenerate.test.ts. Its users are Assay's Z′, the four normalisers, screening, plate diagnostics, the growth window, Gompertz and GR, the qPCR Welch interval (with the ΔCq operands as the scale), the two-way ANOVA with its simple effects, the mixed model and the thermal residual diagnostic (Assay); the log-rank test's counterpart is exact on the counts (riskSetsJoin in src/lib/stats/survival.ts) and slow binding's is the slope's sign (not-rising). src/lib/stats/degenerate-oracle.test.ts drives every case of the 0.16.0 set through them. The CTO may move the module.

Limits are also stated as copy: each app's registry entry has a limits list rendered on its Limits panel, product page and every export's provenance block (contracts §6.2), and many results carry qualifications arrays in their saved records (calibration, qPCR, preparation, intermediate dilution).

The "Unverified" label (D-0025, D-0043)

Contracts §6.1 (decision D33) originally said a method in the unverified state "ships nowhere in this phase". Two decisions make a narrow exception for inputs, not methods:

  • D-0025 (6 October 2026, Bioreactor only, preview only): an input whose source has not been read may ship labelled "Unverified: not yet checked against {source}", as a pill, on every output the input moves. No such value may move a preset's default run. The exception ends at the pre-live pass, when each value is read, replaced, removed or relabelled illustrative.
  • D-0043 (10 October 2026): extends D-0025 to every app on the same conditions; where an app has no pill component, the label is text beside the value or note. It closes Q-25.

Where the label is in use today:

App Value Code and copy
Bioreactor Van 't Riet 1979 non-coalescing kLa constants (a 0.002, b 0.7, c 0.2) and approximate domain src/lib/hosts/transfer.ts:168 (read: false), content/tools/bioreactor-simulator.limits.ts:50, content/apps/bioreactor-simulator.standing.ts:24
Bioreactor Any preset parameter whose source kind is unverified (none of the shipped presets uses it today) src/lib/hosts/presets/types.ts:57, src/components/tools/bioreactor-simulator/sources.ts
Sequence Owczarzy 2008 free Mg²⁺ range of 0.5 to 600 mM, recalled, not read src/lib/seq/tm-bounds.ts, content/apps/sequence-toolkit/tm.ts:38,42

The Van 't Riet label was switched off between 7 and 10 October (commit 732ac12) without a reading (read = true) and restored by the 10 October audit fix (finding M3, commit 2a52165). That episode is the reason the label is tied to a boolean in code rather than to copy alone.

Unread values that do not yet carry D-0043's wording:

  • Assay's bend-point constant k = 4.6805 (Sebaugh and McCray 2003). The copy says "the constant checked by arithmetic; the 2003 paper not yet read" (content/tools/assay-analyser.ts:61) but not "Unverified: not yet checked against …". D-0043 says "the next Assay change checks it". The constant itself is the root of (k − 1) ln k = k + 1 to four decimals.
  • (Resolved 17 October 2026, SCI-145.) The single-stranded DNA A260 factor of 33 µg/mL per AU now cites the sources read: the NanoDrop convention (Thermo Fisher) and Cavaluzzi and Borer 2004, who measured 38 (docs/board/memos/2026-10-17-cso-ssdna-a260-factor.md). Since SCI-146 and SCI-147 (same day) the A260 chip cites its own registry source, a260-conventions, and the three factors are checked against an independent fixture, a260.json (Calc 0.4.0), now a260-2.json (0.5.0), under which the engine refuses a reading at or below zero or not finite (SCI-148, method 1.1.0). Since SCI-149 the A280 concentration and the A260/A280 ratio refuse the same way, against absorbance-a280.json and absorbance-ratio.json (0.6.0).

The public "Unverified" wording itself is on the General Counsel's held list.

Verification states of a method

Every MethodRef carries one of three states:

State Meaning in this codebase Rendering
verified Matched a named external oracle on published fixtures within a stated tolerance; the note says which "Checked against a reference", with the badge sentence from content/apps/<slug>.verification.ts
unit-tested Tested in the repository, no published independent fixture "Tested, not reference-checked"
unverified Withheld; "renders hatched and ships nowhere in this phase" (contracts §6.1) The Sequence hairpin and dimer cards switch to it, with the value withheld, if STRUCTURE_GATE stops holding (content/apps/sequence-toolkit/methods/structure.ts)

The words people read are the CSO's of 14 October 2026 (provenanceUiTexts.verification in content/apps/data-ui.ts; unverified reads "Not checked"); the chips and the methods and checks dialog (D-0085) both use them, and the dialog defines each in one sentence true across every app (methodsSheetText.levels.meaning in content/apps/methods-sheet.ts). "Independent" is not used: Molecule's reference is Python RDKit, the same library in another build, and the exact-arithmetic scripts are procedurally separate but by the same authors. The dialog never counts methods by state for a whole app; each group shows the lowest state among the methods its result goes through (Registry and content).

A method's state is set by hand in its methods file, except for the hairpin and dimer gate, which structure.test.ts recomputes (src/lib/seq/methods-structure.ts, STRUCTURE_GATE).

Units discipline

  • One unit table. src/lib/units/quantity.ts holds one canonical base unit per dimension (g, L, mol, M, g/L, g/mol, cm, cm², cells, cells/L, cells/cm², AU) and every other unit as an exact power of ten from it. The object store's measurement register reads the same table.
  • Exact decimal scaling. scaleByPow10 moves the decimal point of a number's shortest decimal form instead of multiplying floats, so 1.1 µL is exactly 0.0000011 L and back (contracts §5.6).
  • Dimension checks. convert throws when two units differ in dimension. Table import refuses a mass-concentration column for a dose axis because it cannot be converted to molar without a molar mass (src/lib/table/tables.ts).
  • Canonical internal units by domain. Assay concentrations are mol/L and fitted on x = log10(concentration in M); recipes, stocks and dilutions are mol/L, L, g and g/mol; Tm conditions are mM and nM; temperatures are kept as offsets (kelvin and celsius separately, never scaled); the Celsius–kelvin conversion lives in src/lib/units/temperature.ts, outside the scale table, and the recipe pH block carries °C with the unit in the key. OD600 has its own attenuance dimension, so absorbance tools never accept it.
  • Micro sign handling. Input "u", "µ" (U+00B5) and "μ" (U+03BC) all read as micro after NFKC normalisation (normaliseUnitText, unitFor).
  • Hand-offs carry formulas, not just weights. The compound payload carries the molecular formula so the receiving app recomputes the formula weight with its own CIAAW table and shows the sender's weight only "as reported" (src/lib/handoff/payloads/compound.ts).

Divergence: a second, independent unit converter exists in src/components/suite/form/format.ts:254 (convert, with its own UNITS families for mass in Da, length, concentration, data, sequence and time). It divides or multiplies by powers of ten in floating point rather than using scaleByPow10. The two tables overlap on concentrations, so the suite does not strictly have "one unit table". The lab unit converter added in Wave 3 (src/lib/units/unit-converter.ts) is not a third table: it reads quantity.ts through unitFor.

Versions and immutable results

Every library carries a version constant that travels with method records, saved results and links: FIT_VERSION 2.0.0, the protein aligner's ENGINE_VERSION protein-align 1.0.0 (in its correspondence CSV), Molecule's FINGERPRINT_POLICY morgan-r2-2048-described-v1 with DESCRIPTOR_VERSION and RDKIT_VERSION (on every similarity export row and in the approved-drug library), ODE_VERSION 1.1.0, CALIBRATION_VERSION 1.0.0, SEQ_VERSION 1.1.0, UNITS_VERSION 1.0.0, DILUTION_VERSION 1.0.0, TABLE_VERSION 1.0.0, QPCR_VERSION 1.0.0, Bioreactor MODEL_VERSION, TRANSFER_VERSION and SETUP_VERSION 2.0.0. Saved results are read back structurally and never recomputed with today's engine (readCalibrationResult, readMasterMixPlan, readIntermediatePlan, readPreparationResult, readQpcrResult); readers check internal balances and reject corrupt records rather than repair them. Molecule's saved series is the deliberate exception: its record stores the three versions it was computed under, and a mismatch describes the series again rather than mixing results from two policies; likewise the page refuses similarity when the shipped library's versions differ from the engine's (Wave 4). When the assay engine moved to version 2, saved analysis results from version 1 were discarded on load, keeping readings and settings; historical object measurements were kept but labelled for refitting with their old intervals withheld (docs/science/2026-10-scientific-audit-implementation.md, "Intervals and reproducibility"). See Storage and state.

Number formatting and rounding

The rule

Contracts §5.6 (decision D30): store full double precision; round once, at display time, half away from zero on the decimal expansion; compare, threshold and compute deltas at full precision. Exports carry full precision with . as the decimal mark, no grouping, ISO 8601 UTC timestamps.

The shared formatter

src/components/suite/form/format.ts implements the rule. (Contracts §5.6 names src/components/suite/units.ts; that file does not exist.)

Function Behaviour
formatSignificant(value, digits = 4) Intl.NumberFormat("en-GB", { roundingMode: "halfExpand", maximumSignificantDigits }), digits clamped to 2 to 8; no exponent on screen (123456.789 → "123,500"; 0.000123456 → "0.0001235")
formatDecimals(value, decimals, { grouping, trimZeros }) Fixed decimals, half away from zero
formatQuantity(value, quantityId, { extraDigits }) Per-quantity precision from QUANTITIES, plus the "Display precision: default / +1 / +2" preference (extraDigits in src/components/suite/theme/settings.ts)
roundHalfAwayFromZero, roundSignificant The same rounding as a number, for the rare comparison against a displayed figure
formatPercent, formatPercentagePoints, formatCount, formatBytes(bytes, rounding) Percent to one decimal by default; signed "pp" deltas with a minus sign; grouped integers; SI bytes, to nearest by default, "down" for a limit and "up" for the size of a file refused against one, so every refusal shows a size above its limit (the CPO's ruling of 12 October 2026: 4 MiB reads "4.1 MB" as a limit, a refused 4,200,000-byte file "4.2 MB"; FileDrop, fileProblem and the project file's refusals use both)
formatExportNumber(value) String(value): the shortest text that round-trips; "" for null, NaN and infinities; −0 as 0
MISSING "–" (en dash) for any missing or non-finite value, never an empty cell

V8's Intl.NumberFormat rounds the shortest round-trip decimal of a double, not its exact binary value; on Node 22.22 1.005 formats as "1.01" and 2.675 as "2.68" at two decimals, which is what the comment in format.ts claims. (1.005).toFixed(2) gives "1.00" by contrast.

The per-quantity defaults (QUANTITIES, the only place a shared precision is chosen; apps add keys with extendQuantities and may not override a shared one):

Quantity Unit Decimals
Resolution Å 2 (1 for "as reported")
RMSD Å 2
Length in residues residues 0, grouped
Mass kDa / g/mol / MDa 1 / 2 / 1
Monoisotopic mass, m/z Da, m/z 4
logP, Fsp3, PCA coordinate – 2
Salt factor – 4
TPSA Ų 1
QED – 3
Tanimoto – 2
pLDDT – 0
Percent % 1
Association score – 2
Durations years 1
Counts – 0, grouped

Exact formula weights

Formula weights are not floats. Each CIAAW atomic weight is held as an integer in units of 10⁻⁴ g/mol (SCALE = 10_000 in src/lib/units/atomic-weights.ts), a formula weight is an exact integer sum, and roundDecimalString rounds the exact decimal string half away from zero once at display. The documented case: imatinib mesylate is exactly 589.715 g/mol and must show 589.72, which a float sum (589.7149999…) would not.

App-specific helpers that bypass the shared formatter

These were found by reading and running the helpers on Node 22.22; they are display-only but do not follow the §5.6 rule exactly.

Helper Behaviour Effect
formatMolarLog in src/lib/fit/curve.ts:34 Chooses a prefix (M to pM), then Number(value.toFixed(digits)) with 2, 1 or 0 decimals by size Rounds the binary value (2.675 µM shows "2.67 µM"; the shared formatter gives 2.68) and drops trailing zeros (1.005 µM shows "1 µM"). Used for C50 values in reasons, chips and object numbers (src/components/suite/objects/numbers.tsx)
formatSig and roundHalfAway in src/components/tools/lab-calculators/model/display.ts Own significant-figure rounding with a Number.EPSILON nudge, then toLocaleString("en-GB") Agrees with the shared formatter on the cases checked (1.0005 → "1.001") but is a separate implementation
sig in src/components/tools/plasmid-map/format.ts toPrecision, then toLocaleString; powers of ten outside 0.001 to 10⁶ Rounds the binary value and drops trailing zeros
formatValue in src/apps/molecule-workbench/science.ts toLocaleString with fixed decimals Default ICU rounding, consistent in practice
src/components/tools/bioreactor-simulator/format.ts Uses formatDecimals and formatSignificant with the CSO's per-quantity precision (kLa integer h⁻¹, DO 1 decimal %, OUR/CER 1 decimal, RQ 2 decimals, biomass 3 significant figures, times 0.1 h) Follows the rule

Exports: the shared CSV writer (src/components/suite/data/csv.ts) writes numbers with formatExportNumber, so arithmetic noise reaches files verbatim (the 10 October audit's example: "1.0000000000000002 µL" under a "full precision" claim in Calc; still open). Calc's intermediate-dilution panel converts with * 1e6 and * 100 instead of scaleByPow10 (src/components/tools/lab-calculators/panels/intermediate-panel.tsx:23,26), so 7.7 µL round-trips as 7.700000000000001.

Shared scientific libraries

Test counts below were obtained by running each test file of the directory with node --test on Node 22.22.0; all passed. Rows marked † were counted on 11 October 2026 after the Wave 1 and Wave 3 merges, ‡ on 11 October 2026 during the Wave 4 merge; the others on 10 October 2026.

Library Purpose Version Tests (pass)
src/lib/units Unit table, exact scaling, CIAAW atomic weights, formula weights, molarity arithmetic, quantity paste, the lab unit converter and Celsius–kelvin offset 1.0.0 118 †
src/lib/fit 4PL fitting, Levenberg–Marquardt, intervals, generic profile bounds, model comparison, Z′, normalisation, reporting rules, response evidence, standard curves, the stiff ODE solver 2.0.0 (ODE 1.1.0) 102 †
src/lib/kinetics Initial-rate Michaelis–Menten with profile intervals, the inhibition global fit, Ki from IC50 method versions (below) 61 †
src/lib/growth Specific growth rate in a selected window 1.0.0 14 †
src/lib/thermal Apparent Tm of a fluorescence melt 1.0.0 18 †
src/lib/dilution C1V1, stocks, serial dilution, recipes, buffer pK(T) and buffer recipes, master mix, one-intermediate dilution, the allowance rule, the plate preparation sheet 2.0.0 159 †
src/lib/preparation Cell seeding, OD600 estimates (from a factor or paired calibration), centrifuge RCF/RPM, percent solutions, antibody dilution envelope 1.0.0; per-kind records 79 †
src/lib/seq Sequence engine (DNA, protein, thermodynamics, cloning) 1.1.0 551
src/lib/assay/qpcr qPCR relative quantities 1.0.0 17
src/lib/table Delimited text, plate exports, response tables, plate map, well ranges, plate-sheet placement 1.0.0 24 †
src/lib/handoff Hand-off key validators and payloads – 24
src/lib/protein The pairwise protein aligner (Gotoh, BLOSUM62, co-optimal residue pairs; D-0055) and its import boundary protein-align 1.0.0 47 ‡
src/lib/walk Guided-walk keys and links (not scientific) – 4

The Bioreactor model library src/lib/hosts/ is scientific but specific to one app; it is described on Bioreactor.

Units: src/lib/units

API (index.ts re-exports four modules):

  • quantity.ts: UNITS, unitFor, normaliseUnit, unit, unitsOf, baseUnit, scaleByPow10, scaledDecimalString, convert, toBase, fromBase, bestUnit (display only), percentWvToGramsPerLitre (exactly ten times).
  • convert.ts: the molarity arithmetic every bench app shares on base units: molesIn (n = C·V), massOfMoles (m = n·M), molesOfMass, molarityFromMassConcentration (C = ρ/M), massConcentrationFromMolarity, molarityFromMass (C = m/(M·V)), and the guards isPositive, isNonNegative. Nothing rounds.
  • atomic-weights.ts: CIAAW "Atomic Weights 2021" with the 2024 revisions of Gd, Lu and Zr, read 6 October 2026 (Prohaska et al. 2022, doi 10.1515/pac-2019-0603). Interval elements take CIAAW's conventional value (H 1.008, C 12.011, S 32.06, Cl 35.45); others are abridged to five significant figures. Elements with no standard atomic weight (Tc, Pm, Po onward) are absent and refused. Constants table id ciaaw-2021.
  • formula-mass.ts: parseFormula (element counts, nested groups with () or [], adducts joined by ·, . or * with a multiplier, a trailing charge read and reported without changing the mass), hillFormula, formulaMass (exact scaled sum, decimal text, interval elements named), scaledToDecimal, roundDecimalString, formatFormulaMass, parentFraction (salt or hydrate against its parent: imatinib mesylate 0.8370 and a 16.3 percent shortfall). Method record FORMULA_MASS_METHOD.
  • quantity-entry.ts: parseQuantityPaste and parseTypedQuantity accept a pasted "number unit" in the field's dimension and convert it exactly; another dimension is refused with a reason.
  • unit-converter.ts (not re-exported by index.ts): convertUnits, method unit-conversion 1.0.0. Within one quantity, the typed decimal text is rescaled by the difference of the units' powers of ten with integer digits (exact); molarity ↔ mass concentration and amount ↔ mass only through a supplied formula weight of the exact form (floating point, 12 significant figures); anything needing a density, basis or calibration (%, mol/kg, N, U, ppm, ×, OD600, RPM, × g) is refused with its reason. Da is read as g/mol.
  • temperature.ts: convertTemperature, method temperature-offset 1.0.0: t/°C = T/K − 273.15 exactly on the decimal text (BIPM SI Brochure, 9th edition, §2.3.1), refused below 0 K; kept out of the scale table because every toBase caller reads conversion as a multiplication. kelvinFromCelsius for numbers.
  • version.ts: UNITS_VERSION in a module with no imports, so a page that pins the version loads no engine. OD600 is its own attenuance dimension.

Verification: formula weights against RDKit-derived formulas and Python fractions/decimal on 58 fixtures, exact (src/lib/units/formula-mass-fixtures.test.ts, public/verification/lab-calculators/<version>/formula-mass.json); the converter and temperature offset against Python fractions on 44 cases (unit-converter.json, 0.3.0), exact as decimal strings and molar bridges within 1e-9. The 10 October audit ran 10,000 random quantity-entry round trips, all exact.

Known gaps (10 October audit, minor items, confirmed on this tree): "1e-400 mM" parses as 0 and is accepted as 0 because JavaScript reads the literal as 0 before the underflow check (quantity-entry.ts:11); a pasted "5 ng/uL" into a field offering "ng/µL" is relabelled to the first equivalent choice ("mg/L") because the spellings are compared before normalising the "u" prefix.

Fitting: src/lib/fit

The fitting module serves Assay (dose-response and standard curves) and Bioreactor (the ODE solver). Full context of the Assay reporting policy is in D-0028 (docs/board/decisions/D-0028-assay-reporting-policies-after-the-science-audit.md) and on Assay.

Model (four-pl.ts, curve.ts):

y = Bottom + (Top − Bottom) / (1 + 10^((logC50 − x) · Hill)),   x = log10(concentration in mol/L)
Hill < 0: falling curve; Hill > 0: rising curve
drc LL.4: e = C50, b = −Hill, c = Bottom, d = Top

Any parameter may be fixed. With both plateaus free, the solution is written with Top as the higher plateau (canonical). Internal names (logIC50, ic50) are compatibility aliases; human labels say C50 unless the person sets an assay intent of inhibition (IC50) or activation (EC50). The endpoint name never follows the sign of Hill.

Solver (lm.ts, linalg.ts): bounded Levenberg–Marquardt written from Levenberg 1944 and Marquardt 1963 with an analytic Jacobian. Damping is λ times the diagonal of JᵀJ (floored at 10⁻¹² of the largest diagonal), λ starts at 10⁻³, divides by 3 on an accepted step and multiplies by 4 on a rejected one, up to 40 attempts per iteration and 1,000 iterations; trial steps are clamped to the box. Convergence requires the scaled projected gradient |gᵢ|/√(Aᵢᵢ·SS) at or below 10⁻⁶ (4PL setting), or the objective at or below (64ε)²·Σwy²; a small step alone is not convergence. Linear systems are solved by Gaussian elimination with partial pivoting (at most four unknowns).

Starts and search box: the data's own start (Bottom and Top the lowest and highest mean responses, logC50 interpolated where the means cross their midpoint, Hill ±1 by trend) plus Hill magnitudes 0.5, 1, 2 and 4 in both directions; no random restarts. The box is Top and Bottom within 20 response spans of the data, logC50 within 5 log units of the tested range, |Hill| ≤ 50. The converged run with the lowest objective wins.

Identification guards (added after audit finding A1):

  • numericallyFlat: refuse when range(y) ≤ 64·ε·max|y| (no absolute floor, so the guard does not change with units).
  • unidentifiable: |Top − Bottom| ≤ 64·ε·max|y|, Hill = 0, or the column-scaled derivative Gram matrix has a non-finite, negative or > 10¹⁰ inverse diagonal.
  • not-converged: the best run did not converge.
  • A step competitor (|Hill| → ∞ between two adjacent tested concentrations) that beats the best finite fit by more than 10⁻⁹ relative, a Hill on the box face, or a parameter that stays on the face (or keeps lowering the objective) after that face is moved out by four box widths gives no-finite-minimum with a limit of step, beyond-range or plateau-runaway. The step competitor obeys the same fixed plateaus (audit finding B3). The UI and exports call this "numerically unresolved"; the code keeps the legacy status name.

Relative weighting (weighting: "1/y2"): iteratively reweighted least squares. Start unweighted; then, up to 100 times, freeze wᵢ = 1/fᵢ² from the previous fit (every fᵢ must be finite and positive), solve the weighted problem with the weights fixed, and stop when the largest relative parameter change is below 10⁻⁸; otherwise refuse as not converged. Observed responses must be positive. The variance model is SD proportional to the fitted response. No interval, F-test or AICc is computed for relative fits. historicalRelativeLoss reproduces the version-1 direct loss Σ((y − f)/f)² only for archived fixtures and is never selected by the UI.

Intervals (ci.ts, unweighted fits with residual degrees of freedom only):

Wald:     logC50 ± t(0.975, n − p) · SE,   SE² = [(JᵀJ)⁻¹]ₗₗ · SS/(n − p)
Profile:  SS_profile(logC50) = SS_min · (1 + F(0.95; 1, n − p)/(n − p))

The 4PL profile ("profile extra-sum-of-squares", an approximate nonlinear F threshold, not a likelihood interval) brackets outward from the estimate in steps starting at 0.05 log10 and growing by 1.5× to at most 0.5, within 10 log10 units, then bisects to 10⁻⁷. Each refit is warm-started from the previous point, with a full multistart fallback when it fails. Each bound has a status: crossing, search-limit or refit-unresolved; the latter two are not claims of a mathematically infinite interval. No interval is returned when the residual sum of squares is numerically zero.

Generic profile bounds (profile.ts, written for the kinetics intervals; the 4PL keeps ci.ts): the same threshold RSS_min·(1 + F(0.95; 1, df)/df), searched outward in steps of 0.02, 0.04, 0.08, … in the parameter's own units (log units for kinetics), the last step clamped to the box, the crossing found by the Illinois method to 10⁻¹⁰, with the other parameters refitted by a masked LM or a supplied closed form. Side statuses as ci.ts: crossing, search-limit (refitted-at-box, profiled-at-box), refit-unresolved.

Distributions (stats.ts): log-gamma by Lanczos (g = 7, nine terms), the regularised incomplete beta by continued fraction with the modified Lentz method (500 iterations, 10⁻¹⁶), the F upper tail and Student's t from it, the t quantile by bisection to 10⁻¹⁴ relative, and F(p; 1, df) = t((1 + p)/2, df)². Sample SD uses n − 1.

Model comparison (compare.ts): the extra-sum-of-squares F-test at α = 0.05 and AICc = n·ln(RSS/n) + 2K + 2K(K + 1)/(n − K − 1) with K counting the variance. Both are unweighted only; the flat-versus-4PL F-test is an "approximate informational diagnostic" and never the reporting gate.

Z′ (zprime.ts): Z′ = 1 − 3(σpos + σneg)/|μpos − μneg| with sample SDs; refused below two wells per control or with no separation; 0.5 is reported as an observation (Zhang, Chung and Oldenburg 1999), not a gate.

Normalisation (normalise.ts): percent = 100·(y − μneg)/(μpos − μneg) from per-plate control means; raw, percent of control and percent inhibition as named alternatives. Control-mean uncertainty is not propagated into intervals.

Reporting rules (rules.ts, response-evidence.ts), in order: (i) fewer than four distinct positive concentrations or a nonpositive concentration refuses; (ii) response evidence: a two-sided permutation test of dose/response rank covariance (average ranks centred within each plate block, statistic |Σ xr·yr|), exact through eight observations, otherwise 1,999 permutations driven by a xorshift32 generator seeded 0x4d595df4, p = (extreme + 1)/(B + 1); p ≥ 0.05 withholds; constant responses give p = 1; no within-block dose variation gives no evidence; (iii) the C50 needs two tested concentrations on each side; (iv) each free plateau needs two concentrations at or beyond its bend point C50 × 4.6805^(±1/|Hill|); (v) the absolute C50 (where the curve crosses 50 on a percent scale) needs two concentrations predicted on each side of 50; (vi) a profile interval wider than 2 log10 units or with an unresolved bound is flagged "poorly determined" without withholding. R² is computed for unweighted fits and noted, never used as a gate. The p < 0.05 threshold is Drybench policy.

Analysis pipeline (analyse.ts): analyseCurve runs refusal, fit, intervals, rules, R² and a Hill observation (|Hill| > 1.5 steeper, < 0.67 shallower) and samples the curve for drawing over the tested range extended by half a log each side.

Standard curves (calibration.ts, calibration-model.ts): replicate standards averaged per concentration; optional mean-blank subtraction; linear least squares on scaled data (closed form, refused when the scaled slope is within 64ε of zero) or the 4PL fit above (optionally IRLS); unknowns grouped by label and dilution factor, checked against the standards' response range (tolerance 64ε) and concentration range (256ε) before closed-form inversion, then multiplied by the dilution factor. No inverse-prediction intervals. Up to 768 readings. Verified against an independent SciPy 4PL fixture and an exact rational linear fixture, both published (calibration-4pl-scipy.json, calibration-linear-exact.json under public/verification/assay-analyser/0.2.0/ and 0.4.0/; calibration-oracle.test.ts, oracle-fixtures.test.ts). An optional kit protocol (ELISA, Bradford, BCA) adds notes and qualifications only. Calc's OD600 paired calibration reuses fitCalibration. calibration.ts hard-codes its own FIT_VERSION = "2.0.0" (line 6) rather than importing it, and imports copy from content/.

ODE solver (ode.ts): the modified Rosenbrock (W-) formula of Shampine and Reichelt (MATLAB's ode23s method), written from §3.1 of the 1997 paper; second order, L-stable with the exact Jacobian; error estimate h/6·(k₁ − 2k₂ + k₃); defaults rtol 10⁻⁶, atol 10⁻⁹, at most 50,000 steps (then an OdeError); regime switches located by bisection so no step straddles one; watched functions sampled at mid-step, end and the parabola vertex and located to 10⁻⁹ h. rk4Fixed exists only so a test can show why it is not used.

Tests: fit.test.ts, fixtures.test.ts (R nls and drc fixtures), irls.test.ts, calibration*.test.ts, ode.test.ts (with an independent Dormand–Prince helper, dormand-prince.test-util.ts), and science.test.ts, which holds the audit's invariants: constant responses never identify a midpoint; step competitors respect fixed plateaus; perfect non-flat data give a point and no invented interval; exact permutation p values; relative IRLS exposes no interval; the complete five-dose flat null rejects at most 5 percent; floating-point noise is refused at different units.

Stale comments: the four-pl.ts header (lines 10 to 22) still describes 1/Y² weighting as "(y − f)/f minimised directly" and speaks of "no finite minimum"; the rules.ts header (line 10) still describes rule (ii) as the flat-line F-test. The code implements IRLS and the permutation gate. The 10 October audit listed both as open wording items.

Dilution: src/lib/dilution

Module Method Equations and rules
c1v1.ts solveC1V1 Exactly three of c1, v1, c2, v2 given and positive; solves the fourth; diluent v2 − v1; fold c1/c2; refuses a final concentration above the stock
stock.ts massForStock, volumeForMass, concentrationFromMass, parentEquivalent, stockInputOk m = C·V·FW/purity; V = m·purity/(C·FW); parent mass m·MW_parent/FW_form; purity a fraction in (0, 1]
serial.ts planSerialDilution (2.0.0) Tube volume V = R·W·f/(f − 1) for every tube, the last included (constant transfer; 1.0.0 made the last tube R·W, so its point was C/(f − 1)), transfer t = R·W/(f − 1), top tube from stock s = V·C_top/C_stock; 2 to 24 points; fold > 1; flags transfers below the pipette minimum and solvent above the threshold
allowances.ts batchWithAllowances B = N·V·(1 + p/100) + D, each allowance charged once; shared by the master mix, the antibody batch and the plate sheet
plate-sheet.ts, plate-sheet-method.ts planPlateSheet (plate-sheet 1.0.0) A serial plan on a plate: each tube made to keep·f/(f − 1) with keep from the allowance rule, an optional intermediate, a vehicle with the top point's carrier fraction, dose positions from proposePlacement; dead volume once per source; thirteen refusals; planned, not measured
buffer-recipe.ts planBufferRecipe, bufferSetpoint (buffer-recipe 1.0.0) m = C·V·M of the exact form; pH_set(T_prep) = pH_target(T_work) + pK(T_prep) − pK(T_work); only the 15 buffers transcribed twice; 274 to 350 K; target within 1 unit of the listed pK and not within 2 of another step of the same substance
dmso.ts solventFraction, belowPipetteMinimum Solvent fraction f_stock·C/C_stock; transfers strictly below 2 µL flagged (a bench convention set by the CSO on 6 October 2026, "no external citation read"); DMSO above 0.5 percent flagged (rule of thumb)
recipe.ts planRecipe Per component: volume fraction, mass concentration or molarity, weighed or from a liquid stock; refuses invalid purity, invalid stock, missing formula weight, non-finite amounts, and summed liquid volumes above the final volume (tolerance 64ε·V); diluent is null for an invalid recipe
buffer.ts pKAt, pHAt, linearSlope (1.1.0) Goldberg, Kishore and Lennen 2002 equation 12: pK(T) = pK(Tr) − (ΔH°/(R ln 10))(1/Tr − 1/T) − (ΔCp°/(R ln 10))(Tr/T − 1 + ln(T/Tr)), R = 8.31451, Tr = 298.15 K; 34 buffer entries from section 8 (I = 0); outside 274 to 350 K the Calc panel gives no pK (1.1.0; 1.0.0 extrapolated with a note); ionic strength not modelled
master-mix.ts planMasterMix, readMasterMixPlan Per-reaction volumes from volume or stock/target (molarity, mass concentration or an "×" factor; dimensions must match); one balancing diluent; overage = n·mix·percent/100; batch = n·mix + overage + dead volume; mix lines scaled by batch/mix, separate additions by n; transfers below a stated minimum warned
intermediate.ts, intermediate-contract.ts, intermediate-input.ts planIntermediateDilution, readIntermediatePlan Direct when in range; otherwise the feasible set of the intermediate ratio r reduced analytically to one closed interval (including a quadratic reserve bound), choosing the smallest feasible final transfer, then the smallest stock draw; every plan re-checked by the reader with relative tolerance 10⁻¹⁰; no pipette resolution inferred and nothing rounded

Verification: stock, C1V1, serial 1.0.0, recipe and DMSO against Python fractions and decimal on 31 cases to nine significant figures; buffer pK against NumPy on 15 cases at 4, 25 and 37 °C (dilution.json, buffers.json, 0.1.0). Master mix (14 cases) and intermediate dilution (13, an exact brute-force grid) are published under 0.2.0; serial 2.0.0 (10), the plate sheet (22) and the buffer recipe (18) under 0.3.0, each from its own standard-library or NumPy script in pipeline/lab-calculators/. Hashes, versions and script hashes are in pipeline/lab-calculators/fixture-hashes.json, checked by src/lib/fixture-hashes.test.ts. DILUTION_VERSION is 2.0.0 (version.ts, no imports).

Preparation: src/lib/preparation

calculatePreparation dispatches to one module per kind in kinds/ (input and reader, calculation with its method record, receipt). The saved result keeps the envelope id everyday-preparation 1.0.0; each kind's own method record (PREPARATION_METHODS) is carried in the recipe beside it:

  • Cells (cell-seeding 1.0.0): viable density = measured density × viability/100 (when counted total) × counting dilution factor; target per vessel as cells, cells per area × area, or cells per volume × final volume; stock per vessel = target/density; totals with an overage percent; or a split mode by equal parts. Refused when the stock per vessel exceeds the final volume or the plan needs more stock than available.
  • OD600 (od600-cell-estimate 1.0.0): estimate per mL = (OD600 − blank) × dilution × k, with k, its basis (cells, CFU, microsphere equivalents), source and calibrated OD range supplied by the person; no default factor; refused outside the range; never "viable", never fed into seeding (Beal et al. 2020; Stevenson et al. 2016 cited). od600-calibration.ts (od600-calibration 1.0.0) instead fits the person's own OD and count pairs with fitCalibration (linear) and reads the reading back only inside the densities measured.
  • Centrifuge (centrifuge-rcf-rpm 1.0.0): RCF = 1.118 × 10⁻⁵ × r(cm) × RPM², in either direction, with the radius convention recorded and an optional rotor maximum.
  • Percent (percent-solution 1.0.0): w/v (solute g = percent × L × 10), w/w and laboratory v/v; no density assumed.
  • Antibody (antibody-dilution 1.1.0): by concentration or by a 1:N ratio through solveC1V1 over a batch from the allowance rule (overage and one dead volume, none unless chosen), optionally through the one-intermediate planner.

Results are re-read by readPreparationResult, which checks row keys, units, the kind's receipt and balances (relative 10⁻¹⁰). Verification: cells, centrifuge, percent and antibody against Python fractions and decimal on 41 cases (preparation-3.json), the antibody batch on 10 more (antibody-batch.json), OD600 on 14 (od600.json), all 1e-9 relative and published under 0.3.0; the paired OD600 calibration is unit-tested only.

Kinetics, growth and thermal shift: src/lib/kinetics, src/lib/growth, src/lib/thermal

Assay's Wave 1 engines; detail and equations on Assay.

Module Method (version) Equations and rules
kinetics/michaelis-menten.ts michaelis-menten-initial-rate 1.1.0; michaelis-menten-profile-interval 1.0.0 v = Vmax·S/(Km + S) by OLS in log parameters; four distinct positive levels; bound and failed withheld; Km–Vmax correlation; kcat = Vmax/[E] for molar units only; profile bounds on ln Km and ln Vmax at RSS_min·(1 + F(0.95; 1, n − 2)/(n − 2)), Km "poorly determined" past two log10 units or unresolved
kinetics/inhibition.ts inhibition-global-fit 1.0.0 Competitive, uncompetitive, pure noncompetitive or mixed, chosen by the person; Vmax and Km shared; nine starts; any constant at its box withholds the fit; no model selection and no Ki interval
kinetics/ki.ts ki-cheng-prusoff-competitive 1.0.0 Ki = IC50/(1 + [S]/Km), competitive only, typed inputs, [S] and Km in one dimension
growth/window.ts growth-window-rate 1.0.0 μ = OLS slope of ln(y − mean blank) on t in [t0, t1]; doubling time ln 2/μ for μ > 0; withheld for a non-positive corrected reading or fewer than three times; no interval
thermal/apparent-tm.ts thermal-shift-apparent-tm 1.0.0 Sigmoid midpoint by OLS in (F_lo, F_hi, Tm, ln k) and the first maximum of the signed central difference, both reported; nine ordered refusals; disagreement flag above max(1 °C, two median steps); ΔTm per method against a reference; no thermodynamic quantity

Verification: SciPy 1.13.1 and NumPy 2.0.2 (pipeline/assay-analyser/requirements-scipy.txt) or Python fractions, fixtures published under public/verification/assay-analyser/0.2.0/ (growth, Michaelis–Menten, Ki) and 0.4.0/ (profile intervals, inhibition, thermal shift). The R cross-checks named for the last three were not run.

Listed protection: src/lib/exclusivity

The Patent app's engine (Stage I, D-0077): pure TypeScript with no framework import, and a Python second implementation that shares no code (pipeline/patent-exclusivity/trace_reference.py). parse.ts reads the FDA's Orange Book data files, the Purple Book download and the Paragraph IV list from the store's text, header-driven, every expected column it does not find recorded in the store's notes; index.ts joins rows into applications by the exact key (application type and six-digit number, never by name), with the names search and the one join that is not by key, the ANDAs sharing an ingredient and dosage form and route; trace.ts applies the seven rules of the CSO's memo of 13 October 2026 and returns every date with its rule, its flags and its row citations, plus a summary that is a set (the latest substance patent, the latest listed patent, the latest exclusivity, the earliest and latest of all), never one loss-of-exclusivity date. The only arithmetic is NCE-1, the NCE date minus one calendar year (dates.ts, 29 February clamped to 28), marked computed; the *PED row is read as the FDA lists it and six months are never added; a date the FDA wrote in words keeps its text with a null ISO day. Detail, the rules and the limits on Patent; the oracle on Oracles and verification.

Sequence, construct cropping and ligand efficiency: src/lib/seq, src/lib/protein, src/lib/chem

The largest library. The barrel index.ts is for the workspace and tests; task islands import single modules. Each method record lives in methods-*.ts.

Area Modules Method and reference Oracle (verification set)
Alphabets, parsing iupac.ts, parse-core.ts, parse.ts, revcomp.ts, pack.ts IUPAC (Cornish-Bowden 1985); FASTA, multi-FASTA, GenBank; offenders named by position; 2-bit link codec Unit tests
Genetic codes, translation codes-data.ts (generated from NCBI gc.prt v4.6), codes.ts, translate.ts NCBI tables; ambiguous codons read through every base; dual-use stops of tables 27, 28 and 31 refused for stop-dependent operations Biopython, 113 cases, exact
ORFs orf.ts NCBI ORFfinder 0.4.3 conventions; 75 nt minimum; nested ORFs hidden and counted NCBI ORFfinder, 18 cases, exact
GC, motifs gc.ts, motif.ts G, C, S count as GC; A, T, U, W count in the denominator; other codes excluded and reported; undefined rather than 0 when nothing counts Unit tests
Nucleic acid mass dna-mw.ts Residue formulas from PubChem nucleotide monophosphates summed on the CIAAW table; single-stranded linear 5′-OH default; options for 5′-phosphate, circular, double-stranded Python fractions, 5 cases, within 0.01 g/mol
Oligo Tm tm.ts, tm-bounds.ts SantaLucia 1998 nearest-neighbour ΔH°, ΔS°; Tm = ΔH°/(ΔS° + R ln(C/x)) − 273.15, R = 1.987; primer-excess (x = 1) or equal-strands (x = 4) models; Owczarzy 2004 monovalent, Owczarzy 2008 Mg²⁺ with the R = √[Mg²⁺]/[Mon⁺] regimes (0.22, 6) and dNTP chelation (Ka 3 × 10⁴ M⁻¹), SantaLucia 1998 entropy correction; refusals and fitted-range notes per the CSO ruling of 10 October 2026 Biopython 1.88, 45 oligos, within 0.1 °C
Hairpins and dimers structure.ts, structure-params.ts, structure-drawing.ts SantaLucia and Hicks 2004 with 1998 stacks, Bommarito 2000 dangling ends, NNDB hairpin terminal mismatches; special triloop and tetraloop bonuses omitted, so GNA/GNRA hairpins appear less stable primer3-py (ntthal 2.6.1), 15 hairpin and 16 dimer fixtures within 0.4 and 0.1 kcal/mol under STRUCTURE_GATE
Protein numbers protein-mass.ts, pi.ts, absorbance.ts, constructs.ts ProtParam average residue masses by default (CIAAW-summed table selectable); Bjellqvist pI as ProtParam computes it; ε280 by Pace 1995 (5,500 Trp, 1,490 Tyr, 125 cystine); A280 by Beer–Lambert; A260 rules of thumb 50/40/33 µg/mL per AU Expasy ProtParam, 22 proteins (MW within 0.01 Da, pI within 0.01, ε280 exact)
Construct cropping construct-cropper.ts (uses protein-mass.ts, pi.ts, absorbance.ts) Suggests construct boundaries from AlphaFold pLDDT (terminal residues below 50, Jumper 2021) and reports the tagged construct's MW, ε280, pI and aliphatic index (Ikai 1980) through the suite's own protein engine. A tag is cleavable only where it names a protease, so only those have a post-cleavage species; a protease tag asked for at the C-terminus is refused; a construct that would not start with Met is given an initiator Met and says so Biopython ProtParam, pipeline/protein-explorer/fixtures/construct-cropper-v1.json and construct-cropper-v2.json
Ligand efficiency chem/medchem-sar.ts LE = 1.3642·pActivity/HAC (Hopkins 2004; the factor of Assay's efficiency.ts, 298.15 K), LLE = pActivity − cLogP (Leeson and Springthorpe 2007), LELP = cLogP/LE (Keserű and Makara 2009); a monoprotic Henderson–Hasselbalch cLogD only from a user-entered pKa, with that limit stated; no number until potency is entered; no CNS MPO RDKit, pipeline/molecule-workbench/fixtures/medchem-v1.json
Peptide SMILES peptide.ts Linear L-peptide as RDKit MolFromSequence draws it RDKit, 101 cases (89 identical canonical SMILES, 12 refusals)
Exact matching pcr.ts, fragment-search.ts, sequence-correspondence.ts Supplied-pair PCR product by exact annealing; literal fragment search; revision transforms with bounded edit traceback Independent window and hand derivations (pipeline/sequence-toolkit/exact_matches.py, revision-transforms.py), exact
Primer design primer-design.ts, primer-design-contract.ts, methods-design.ts Bounded flank enumeration (bounded-flanks-v1), template up to 50,000 bases, 24 candidates per side, 5 returned pairs primer-design.json (Sequence 0.5.0): 9 design cases and 4 refusals from an independent Python derivation with Biopython's Tm, one worked by hand; MethodRef primer-design-bounded-flanks verified
Plasmid engines enzymes.ts, digest.ts, methylation.ts, gel.ts, dna-amounts.ts, features/*, ds-fragment.ts, restriction-cloning.ts, golden-gate.ts, gibson.ts, crispr.ts See Plasmid Biopython, McClelland et al. 1994 Table I, Python fractions, hand-worked and independent enumerations

Full detail is on Sequence and Plasmid.

Stage F's gap-aware protein correspondence is independent of the legacy AlphaMissense offset placement. Method protein.residue-map@1.0.0 is specified in pipeline/protein-explorer/residue-map/method-v1.md; the pinned Biopython oracle enumerates all co-optimal global/free-end alignments before classifying invariant standard-residue pairs. Its immutable reference is published as Protein 0.5.1 (v1/0.5.0 retained) before implementation, without a live registry or app-version change. The pure src/lib/protein/residue-map.ts foundation returns versioned, reciprocal raw correspondence, exact/differs/unmapped states and exact-only null transfer maps; missing sequence and typed refusals remain distinct. It preserves actual sequences and optional unvalidated provenance. Its sole runtime consumer is a cancellable dedicated worker. The strict source-confidence parser and exact-only current-UniProt projection have a separate frozen Biopython reference published as Protein 0.6.0 before implementation. Source colours remain in model coordinates; transferred tracks/cards and range means omit every non-exact or missing score and state contributing n/selected N. Unverified fragment domains are withheld. Legacy AlphaMissense and experimental SIFTS rules are unchanged. See Protein and Oracles and verification.

qPCR: src/lib/assay/qpcr

calculateQpcr (quantity.ts, method qpcr-relative-expression 1.0.0, citing Pfaffl 2001 and Hellemans et al. 2007):

technical mean Cq per run, biological sample and assay (accepted, measured wells only)
log2 Q = −meanCq · log2(E)                      E = 2 for ΔΔCq; entered efficiencies otherwise (90 % → 1.9)
normalised = log2 Q(target) − mean(log2 Q(references))     (geometric normalisation)
log2 fold change = normalised − mean(normalised of the run's declared calibrators)
fold change = 2^(log2 fold change);  group = geometric mean over quantified biological samples

It refuses without confirmed biological identifiers, a technical-spread limit, declared target and reference assays, and an efficiency for every assay; missing or undetermined wells leave a sample unresolved rather than entering a partial mean; technical spread never auto-excludes. No interval and no inter-run normalisation is computed. Verification state unit-tested: the tests compare with 60-digit Python Decimal values written by src/lib/assay/qpcr/oracle.py, but no verification set is published.

Tables and plates: src/lib/table

Decoding with byte-order marks (UTF-8, UTF-16 with and without a mark), delimiter detection (tab, comma, semicolon), RFC 4180 fields, a decimal comma when the delimiter is not a comma (parseNumber), plate-export profiles (grid, softmax-pro, gen5) for 96- and 384-well plates read from the plate block alone with header metadata discarded, long and wide response tables with units in headers, the plate-map model, and an ImportReview record of every rejected or unresolved cell with its source position. Method plate-export-parser, unit-tested. The 10 October audit noted that a wide table whose headers are read labels (OD_450,OD_570) parses as long with the second column ignored (disclosed).

Cell lines: src/lib/cells

Cell line resolution (E10, D-0099; the CSO's ruling F5 of 17 October 2026), method cell-line-resolution 1.0.0, verified (content/apps/cell-lines.methods.ts). resolve.ts is pure and reads the served Cellosaurus snapshot through a CellLineSource (snapshot-source.ts fetches it in the browser). By accession, exact (read in upper case; a secondary accession leads to its record); by name, exact on the primary name or a synonym after lower-casing and removing whitespace and hyphen characters only, with no fuzzy matching; a normalised name more than one record carries returns them all as candidates and chooses none; anything else, a listed misspelling included, is unresolved. No number is computed. The STR profile is shown as Cellosaurus's reference values; nothing compares STR profiles (the CSO's ruling: that needs its own oracle against the Tanabe and Masters algorithms). Checked against pipeline/cellosaurus/oracle.py on pipeline/cellosaurus/fixtures/resolution-56.0.json (100 cases, zero wrong assignments, 6 unresolved) by resolve.test.ts; see Oracles and verification. passport.ts holds a record's quoted fields by value; the limits are in content/apps/cell-lines.ts, first "This record does not check the identity of the line in your lab."

Hand-off payloads: src/lib/handoff

Scientifically relevant rules:

  • Links carry inputs, never derived results; validators check shape and size (params.ts).
  • The compound payload carries formulas so the receiver recomputes weights; a salt is never sent as its parent's mass (payloads/compound.ts).
  • The recipe payload carries inputs and pins the method versions and constants tables it was made with; the record recomputes on open (payloads/recipe.ts).
  • The timeseries payload requires origin (simulated or measured) so a simulated series can never return as data; it caps 16 series and 10,000 points (payloads/timeseries.ts).

See Storage and state.

Index of app methods

Per-app detail is on each app page. "Published set" means the registry's verificationSets and files under public/verification/<slug>/<version>/.

App (registry version) Page Principal methods Oracles and published set Notes
Protein (0.2.0) Protein UniProt, PDB and AlphaFold evidence; pLDDT and PAE display; residue placement across releases (affine alignment +2/−3/−5/−1, 10,000-residue cutoff); AlphaMissense per-residue mean with n, withheld on any reference-letter mismatch (DEF-W4-2) or model difference (DEF-W4-3); one-substitution AlphaMissense check; pairwise alignment (Gotoh, BLOSUM62, gap 10 + 0.5 (k − 1), end gaps free, co-optimal residue pairs); ProtParam mass, pI, ε280 via src/lib/seq ProtParam fixtures (src/lib/seq/fixtures/protparam.json); Wave 4 Python oracles for AlphaMissense (5 entries), substitution (25 inputs) and alignment (Biopython 1.88, three fixture sets; EMBOSS Needle cross-check); no published set Exact-only current-UniProt pLDDT and predicted focus through the bounded F-1b worker; legacy AlphaMissense placement remains separate
Molecule (0.2.0) Molecule RDKit.js 2026.03.6 descriptors; generic NumHBD/NumHBA and historical Lipinski counts kept separate; QED port; PAINS; Morgan/Tanimoto under policy morgan-r2-2048-described-v1 with same-compound rows left out; series table with pIC50 for molar IC50 only; descriptor PCA; Rule of 5 and Veber as heuristics Python RDKit 2026.03.6 on all 2,197 library molecules, all values match (docs/science/evidence/molecule-library-2026-10-10/); fingerprint_policy.py and series_reference.py (Wave 4); NumPy PCA; no registry set descriptorVersion 2.0.0-num-hbd-hba
Pipeline (0.1.0) Pipeline ClinicalTrials.gov aggregation, registry coverage, timeline by phase, modality and sponsor heuristics, stopped-trial categories, FDA precedent Own sets in content/apps/drug-pipeline-explorer.verification.ts (products, crl, section14, examples); coverage_oracle.py on a frozen search (Wave 4); none in the registry "Registered clinical-development landscape"; counts are trials, not assets
Patent (0.1.0) Patent The seven listing rules over the FDA's Orange Book, Purple Book and Paragraph IV files: listed patent expiries with their flags, the *PED row as listed, exclusivity codes with the FDA's definitions, NCE-1 (the one computation), the Paragraph IV row as listed, the generics by ingredient and dosage form, delist and use-code flags; a summary that is a set, never one date trace_reference.py, a Python second implementation, on the synthetic fixture (expected.json, 7 applications, field by field); 46 engine tests Synthetic store until the first capture (DAT-16); rules unreviewed by a regulatory-affairs reviewer (SCI-124, Q-19)
Cell (0.1.0) Cell Educational tour over eight deposited structures (3AFA, 8XSO, 6QDV, 7ZNK, 6Y0G, 6OLE, 7TUB, 2DN1); no computation Structure records; no published set A montage of different systems, not one molecule
Calc (0.2.0) Calc Formula weight, stock, C1V1, serial (2.0.0), plate preparation sheet, recipe with pH line, buffer pK(T), buffer recipe, A280, A260, master mix, intermediate dilution, preparation (five kinds), unit converter RDKit (58), Python fractions (31; 0.2.0 master mix 14 and intermediate 13; 0.3.0 serial 10, plate sheet 22, preparation 41, antibody batch 10, OD600 14, converter 44), NumPy (15; buffer recipe 18), Biopython (22); 0.4.0 A260 14, 0.5.0 A260 19 and 0.6.0 A280 14 and ratio 10 (Python fractions) The 2 µL rule and the OD600 paired calibration unit-tested; fixtures for 0.3.0 were recorded with Python 3.13.16
Sequence (0.5.0) Sequence Translation, ORFs, Tm and salt corrections, hairpins and dimers, nucleic acid and protein mass, pI, ε280, peptide SMILES, PCR product, fragment search, revision review, primer design Biopython, NCBI ORFfinder, ProtParam, primer3-py, RDKit, exact derivations Every method has a record and a published set since 11 October 2026
Assay (0.2.0) Assay 4PL, bounded LM, IRLS relative weighting, Wald and profile intervals, F-test, AICc, Z′, normalisation, reporting rules with permutation evidence, absolute C50, standard curves, qPCR, plate parser; replicate summaries, growth window, Michaelis–Menten with profile intervals, inhibition global fit, Ki from IC50, thermal-shift apparent Tm R 4.3.3 nls, drc 4.0-0, MASS; SciPy IRLS reference; R 4.6.1 cross-check of exported scripts; SciPy 1.13.1 and Python fractions for the Wave 1 sets (0.2.0, 0.3.0 plate walk, 0.4.0) qPCR unit-tested; IRLS reference not in the 0.2.0 manifest; R cross-checks of the three 0.4.0 sets not run
Plasmid (0.7.0) Plasmid Restriction sites and digests, dam/dcm blocking, diagnostic digest picker, schematic gel, feature annotation, sequencing-primer sites, DNA amounts, SnapGene reading, double-stranded ends, restriction cloning, Golden Gate, Gibson-style assembly, SpCas9 guide sites Biopython 1.88, McClelland 1994, Python fractions, hand-worked assemblies, independent SpCas9 enumeration No accepted CSO review of the rebuilt Wave 2 items is recorded
Bioreactor (0.2.0) Bioreactor Uptake-first growth with maintenance and Luedeking–Piret product, Rosenbrock integration, dissolved oxygen and exhaust gas balances, Van 't Riet kLa, scale-up rules SciPy 1.17.0 Radau at rtol 10⁻⁹; USGS DOTABLES for oxygen solubility Exploratory teaching model; Q-19 and held-out process data outstanding
Education Education tools Fourteen teaching models (for example dose-response, dilutions and buffers, binding and inhibition, growth and oxygen) 27 model tests; all fourteen models re-derived by the 10 October audit Not one of the ten registry apps (APP_NAMES in content/app-names.ts); two tools fail the entry rule (audit M11, set aside by the CEO)

Verification evidence

Published verification sets: public/verification/

Each app that publishes sets has one directory per release, with a manifest.json (generating scripts, script hash, recorded date, per-file SHA-256) and the fixture files the docs page links to through verificationFileUrl(slug, version, name) (content/apps/routes.ts). The registry's verificationSets name, for each set, the methods covered, the oracle (which must be listed in oracles[]), the fixture count, the tolerance, the date and the file; content/apps.test.ts checks that shape and that exactly Calc, Sequence, Assay, Plasmid and Bioreactor publish sets.

App Versions present Notes found on this tree
assay-analyser 0.1.0 to 0.4.0 0.2.0 adds the PRISM erlotinib example, the relative IRLS reference and the seven Wave 1 oracle fixtures; the shared files are identical to 0.1.0; relative-irls-reference.json is not listed in the 0.2.0 manifest. 0.3.0 holds the plate walk's refit row only. 0.4.0 holds byte copies of the seven Wave 1 fixtures and the profile, inhibition and thermal-shift fixtures. The registry version is still 0.2.0; sets name their directory with version
bioreactor-simulator 0.1.0, 0.2.0 0.2.0 holds the model-2 goldens (feed dilution, stripping, flow basis)
lab-calculators 0.1.0 to 0.6.0 0.1.0 has no manifest.json. 0.2.0 holds byte copies of the four 0.1.0 files and the master-mix and intermediate fixtures, with a manifest listing only those two. 0.3.0 holds the ten Wave 3 fixtures with a manifest. 0.4.0 holds the A260 fixture with a manifest (SCI-147); 0.5.0 holds its successor with the refusals, which supersedes the 0.4.0 zero case (SCI-148); 0.6.0 holds the A280 concentration and A260/A280 ratio fixtures (SCI-149). pipeline/lab-calculators/fixture-hashes.json records every file with its version, and src/lib/fixture-hashes.test.ts checks each published copy
plasmid-map 0.1.0 to 0.7.0 ds-ends arrived in 0.2.0, restriction cloning in 0.3.0, Golden Gate in 0.4.0, Gibson in 0.5.0 and CRISPR in 0.6.0; 0.7.0 revises those five files
sequence-toolkit 0.1.0 to 0.4.0 0.2.0 is identical to 0.1.0; 0.3.0 adds PCR product, fragment search and selection; 0.4.0 adds revision transforms

Tolerances by set are listed on Oracles and verification.

Write-once protection

scripts/check-write-once.mjs (CI job check-write-once) protects every manifest.json and fixture-hashes.json, the TypeScript manifests (src/lib/hosts/presets/manifest.ts, src/lib/seq/enzymes-manifest.ts, src/lib/seq/features/library-manifest.ts, src/lib/seq/features/primers-manifest.ts), JSON and CSV under fixtures/ and golden/, JSON under public/verification/ and public/data/plasmid-map/, and versioned preset files. A protected file may not change or disappear; a manifest entry may only move to superseded or withdrawn with a dated reason. The 10 October audit fix branch recorded 185 protected files. Details: Data pipeline.

docs/science/evidence/

Path What it holds
original-audit-inventory.json SHA-256 of the 7 October audit archive and its members; the archive itself was moved outside the repository at the science owner's request
original-audit-results/ Five small JSON results copied byte for byte from that archive: adversarial cases and summary (the flat-response A1 failures), the fixed-plateau case (B3), and the 400-run assay noise study
implementation-verification/ Logs, ledgers and hashes for the 7 October corrections and follow-up: unit, build, type, lint, size, browser, performance and worker-gate runs by commit (42ed911, 91ab65f, ca9055f, ad8417f, 443d4ce, 7fb874b, bb00e28), the event-scanner equivalence check (66 runs hashed before and after), and r-crosscheck-2026-10-07/ (R 4.6.1, drc 4.0.0 execution of the exported scripts C01, C24 to C27)
molecule-pca-reference.json NumPy PCA on Python RDKit descriptors of the 2,197 shipped SMILES: loadings within 4.22 × 10⁻¹⁵, explained fractions 41.935 % and 24.586 %
molecule-library-2026-10-10/ The committed whole-library Python RDKit reference (gzipped) and the verifier's output: all values match, largest QED difference 2.78 × 10⁻¹⁶, 124 PAINS molecules
molecule-fingerprint-policy-2026-10-10/ The Wave 4 key spike on which Morgan settings RDKit.js and Python RDKit honour
protein-align-2026-10-11/ The EMBOSS Needle cross-check of the protein aligner: four runs, every figure equal to Biopython's and the engine's

The implementation-verification README is explicit that these logs are software evidence and "do not establish empirical physiological prediction, confidence-interval coverage for arbitrary assay designs or clinical validity". Retained third-party output (R and drc citation text) is on the General Counsel's held list.

The scientific audits

All three records are kept unchanged; later records append. They are the place to start before changing any scientific logic.

7 October 2026: independent audit

docs/science/2026-10-scientific-audit.md, on commit 42372f1. AI-assisted, procedurally independent but not blinded. Scores (out of 10): Assay 4, Bioreactor 5, Molecule 8, Protein 8, Sequence 8, Calc 8, Plasmid 8, Pipeline 8, Cell 9.

Finding Summary
A1 (critical) Exactly flat responses received a C50 and, with residual df, an interval of about ±4.77 × 10⁻⁸ log10 units around an arbitrary midpoint
B1, B2, B4 The relative loss, its intervals and the flat-line F-test did not form one probability model; the flat test is nonregular; solver statuses overstated mathematical conclusions
B3 An unconstrained step competitor rejected fits with a fixed plateau
B5 Protein residue tracks misplaced after an internal insertion
B6 Molecule's generic HBD/HBA were historical Lipinski counts
B7 Recipes accepted stocks summing to more than the final volume
B8 Tm returned −273.15 °C or silently uncorrected values for invalid salt inputs
B9 NCBI tables 27, 28, 31 had no stops; stop-dependent ORFs were unqualified
B10 to B13 Bioreactor: infeasible yields accepted, feed oxygen dilution and stripping missing, a wrong Ks half-saturation factor, event-boundary reproducibility failures
C1 to C11 Wording: C50/IC50/EC50, relative-error model, InChI tautomers, AlphaMissense, annotation heuristics, DpnI, Pipeline scope, Bioreactor labels, special loops, Cell framing

Section F gives exact repair recipes and section I a permanent regression-suite specification.

7 October 2026: implementation and follow-up

docs/science/2026-10-scientific-audit-implementation.md records the corrections: identification guards and constrained step competitors; the within-plate permutation gate replacing the F-test as the reporting gate; IRLS for relative weighting with relative intervals unavailable; profile bound statuses; C50 naming and assay intent; result-version invalidation; NumHBD/NumHBA split with a rebuilt PCA; conservative protein placement; Tm and genetic-code refusals; recipe feasibility; Bioreactor feed dilution, stripping, yield feasibility, actual and reference gas-flow bases, the corrected S50 = Ks(1 + 2·Yxs·ms/μmax) and new model-2 goldens. On 7 October npm test passed 1,920 tests.

docs/science/2026-10-scientific-audit-followup.md records the follow-up branch (PR #6): R cross-checks executed (largest midpoint difference 7.93 × 10⁻⁵ log10 against a 4.3 × 10⁻⁴ tolerance), the write-once guard added, performance-gate measurements by commit, and a documented stop when Turbopack could not be built in the environment (no Webpack result was substituted as verification).

Remaining after both: calibrated assay inference (audit F3), plate and biological-replicate inference, product composition and a separate product yield in Bioreactor, jointly fitted presets, empirical power, kLa and Reynolds support, fuller protein alignment provenance, richer loop thermodynamics, and Q-19.

10 October 2026: audit of the 7 to 10 October changes

docs/science/2026-10-10-scientific-audit-of-the-7-to-10-october-changes.md, on main at a1aac9f. The engines added in the period (master mix, intermediate dilution, calibration, the permutation gate, PCR product, fragment search, Bioreactor model 2, the HBD/HBA split, fourteen Education models) were re-derived and checked against independent oracles written for the audit, with no wrong number found in a core engine. The defects were at the display, recording and provenance layers.

Finding Status (section 8 of the record; fixes merged to this branch)
C1 Sequence recorded a different primer check from the one shown Fixed, 7f0699a
C2 Molecule judged generic counts against the Rule of 5 in three places Fixed, 7f0699a; the approved-drug population band for Lipinski counts is not shown pending a library rebuild
C3 Bioreactor Setup printed the pre-audit Ks factor Fixed, 7f0699a
C4 Study AI packet builder crashed in its only test Fixed, 7f0699a
C5 Molecule accepted biologics by name Fixed, 7f0699a; D-0042 keeps the rule
M1 Eleven methods or surfaces shipped without a published independent fixture, methods entry or decision Open; partly addressed for Sequence (PCR product and fragment search fixtures and method records in 0.3.0 and 0.4.0)
M2 Undeclared literature services and unattributed ChEMBL names Open; accepted for now by the CEO, Q-26
M3 Van 't Riet "Unverified" switched off without a reading Fixed, 2a52165
M4 Tm accepted meaningless conditions Fixed under the CSO ruling (c3a06ee, 75a65e9, 9819161); Q-24 open
M5 Calc purity silently clamped to 100 % Fixed, 2a52165
M6 Exported R IRLS script reversed on drc before 4.0.0 Fixed, e001f08
M7 Result comparison diffed raw payloads Fixed, 7f0699a
M8 Intermittent rescued E. coli golden Diagnosed; rule put to the CSO as Q-23; no tolerance or golden changed
M9 Protein copy claimed a PAE image no code renders Copy fixed, rendering parked
M10 Molecule exports lacked descriptor provenance Fixed, dated whole-library run committed
M11 Two Education tools fail the entry rule Open; set aside by the CEO
Section 4 minor items Open, except the test command and CI (44a4003) and a product name outside content/app-names.ts (175eb8b)

The record's section 7 makes working-practice observations an acquirer should weigh: several verification claims in 9 and 10 October records were made with a Node flag CI does not use or on branch trees other than main; corrections reached engines and documentation but not consistently the display layer or older code paths; and some earlier rulings were reversed without a decision record.

How scientific claims are controlled

The CSO seat

The Chief Scientific Officer's job description is docs/board/seats/cso.md; the agent is .claude/agents/cso.md (read-only tools). The remit: scientific correctness of every app; methods pages; verification datasets and expected outputs as release gates; extending provenance from every panel to every number; scientific review of data sources; acceptance thresholds for entity resolution and the modality classifier; oracles for the bench engines; review of any copy or chart that makes a scientific claim. Standards: "A result is not correct until it matches a reference. A limit is stated before a feature is used. 'Unclassified' is a valid answer; a guess is not."

The CSO advises; the CEO decides. CSO rulings are recorded as memos under docs/board/memos/ (for example 2026-10-06-cso-oracle-rulings.md, 2026-10-06-cso-solver-pins.md, 2026-10-10-cso-tm-condition-ranges.md, 2026-10-10-cso-wave2-cloning.md) and cited in code comments by date ("CSO ruling, 6 October 2026, item 9"). 2026-10-10-cso-wave2-review-not-accepted.md is marked "Not accepted as a ruling" because a different model served the review; it is kept as a defect list.

Gates

The charter (docs/board/charter.md §6) gives the CSO the gate "Any release that claims a verification dataset or reproducible exports: the dataset and expected outputs exist and are published", and a share of the gates for any new data source and any new app. Under D-0014 these gates are checked at the pre-live pass, not before work, while the suite is in development. Q-19 asks for a named human reviewer before any app leaves preview: the CEO may sign for general molecular biology and bench apps; Assay, Bioreactor, antibody work, Molecule's medicinal-chemistry judgements and the Pipeline and Precedent data models need independent review. It is open.

Copy rules

  • Forbidden regulatory phrases. content/apps/regulatory.ts defines FORBIDDEN_PHRASES ("Part 11 compliant", "FDA validated", "GxP certified", "GxP compliant", "audit trail") and NEGATION_ALLOW; scripts/forbidden-phrases.mjs scans every .ts, .tsx, .mts, .mjs, .js, .md, .txt and .json file under content/ and src/, and content/apps.test.ts fails on any hit. Bare "validated" is not forbidden, so limits can say what has not been validated. The pattern requires whitespace between a phrase's words, so a hyphenated form such as "GxP-certified" is not caught (the test at content/apps.test.ts:397 expects exactly this). docs/ is not scanned.
  • No "validated". Verification copy files say "checked against" and name the oracle, version, case count, tolerance and date (for example content/apps/plasmid-map.verification.ts); the Pipeline verification module states "Never 'validated'".
  • Copy lives in content files, so scientific wording is reviewable in one place per app (content/apps/<slug>*.ts, content/tools/<slug>*.ts); see Registry and content.
  • Methods pages. Each app's docs page renders its methods, equations, constants and oracles from the method records and verification files, so numbers are not typed twice (src/components/suite/marketing/verification.marketing.tsx, content/apps/<slug>.docs.ts).

Copy found to contradict the code on this tree:

  • (Closed 11 October 2026, Wave 6 item 1.) The Sequence product FAQ answered "Can I design primers here?" with "No" while the workbench shipped a bounded primer-design panel; the FAQ now describes the bounded design and its limits, and the design is a public operation with its page and published fixture.
  • Molecule's verification note cites a "pipeline run of 7 October 2026" (content/apps/molecule-workbench.methods.ts:20); the committed whole-library evidence is the 10 October 2026 run.

Open scientific risks

Ordered by the reviewer's judgement of consequence for an acquirer. Owners and question numbers are from docs/board/open-questions.md.

  1. No human scientific sign-off (Q-19, open). Every audit and CSO ruling is AI-assisted. Nothing has been reviewed by a named practising scientist or an external bioprocess specialist.
  2. Methods shipped without an independent published fixture (audit M1, partly addressed). Waves 1 and 3 published fixtures for calibration, master mix, intermediate dilution, preparation and every new method. qPCR, the 2 µL rule and the OD600 paired calibration still run (A260 gained its fixture on 17 October 2026, SCI-147) as unit-tested or unrecorded (Sequence primer design gained its record and published set on 11 October 2026), and the relative IRLS reference is not in the Assay 0.2.0 manifest. This conflicts with roadmap rule 1. The Wave 1 continuation's profile-interval, thermal-shift and inhibition fixtures rest on SciPy alone; their R cross-checks were not run.
  3. Assay inference is approximate (audit F3, open). Intervals assume independent Gaussian errors of constant variance and condition on estimated control means; relative fits have no intervals; the permutation gate's size is checked exhaustively only for small designs; plate, control and biological-replicate inference is not modelled. The Monte Carlo p depends on row order (10 October audit, minor).
  4. Bioreactor is an exploratory teaching model. Presets combine parameters from different strains and studies and are not jointly fitted; product is biomass-equivalent in composition; Np = 5 is nominal; no held-out process data exist. Q-23 (how the rescued E. coli golden treats a located crossing) is open.
  5. Unread sources under D-0025/D-0043. Van 't Riet 1979 non-coalescing constants and domain; the Owczarzy 2008 Mg²⁺ range and the SantaLucia 1998 range (Q-24); the Sebaugh and McCray 2003 bend constant; the NNDB licence line. The last two do not yet carry the D-0043 label. The exception ends at the pre-live pass.
  6. Provenance is per panel, not per number (Q-16). The contracts' enumeration test does not exist.
  7. Undeclared services and share-alike names (M2, Q-26). Scientific provenance as well as licensing is affected: some displayed names cannot be traced to a declared source on the page that shows them.
  8. Wave 2 cloning engines lack a recorded CSO acceptance. The only Wave 2 CSO review memo is marked not accepted; the rebuilt Golden Gate, Gibson, restriction cloning and CRISPR engines ship in Plasmid 0.7.0 with independent fixtures, but no accepted review record was found in docs/board/memos/.
  9. Display-layer drift. The 10 October audit's critical findings were all display or recording defects beside correct engines. Remaining examples: helpers that round outside the shared formatter (formatMolarLog, Plasmid sig), a second unit converter, float noise in Calc exports, stale engine header comments, and Tm cards on Plasmid and Sequence task pages without the limit line (a recorded exception in the CSO's 10 October ruling).
  10. Wave 4 (discovery line) open items. No practising scientist reviewed any Wave 4 output (CSO merge review). F-1b now supplies exact-only pLDDT and predicted focus; AlphaMissense retains its conservative legacy placement. The original aligner fixtures enumerated up to 8 co-optima; F-1a adds a fully enumerated 4,096-optimum correspondence case. An X–X alignment column still counts as identical but not similar (DEF-W4-4). AlphaMissense's bulk source file states CC BY-NC-SA 4.0 while the per-protein CSV the app reads is recorded as CC BY 4.0; held for the General Counsel, and a non-commercial outcome would mean a rewrite of the per-residue means and the substitution check at the pre-live pass.
  11. Thermodynamic model limits. Hairpin and dimer ΔG omit special loop bonuses (affected hairpins appear less stable); results are minimum ΔG°37 within the supported search, not a concentration-dependent ensemble; agreement with ntthal is shown on 31 fixtures only.

Repeat setup (contract 06, 17 October 2026)

Repeating a saved Calc serial dilution → Assay dose-response setup with a replacement material adds no method. It checks the reviewed inputs explicitly before the existing serial-dilution 2.0.0 and plate-sheet 1.0.0 engines run, so none of the calculators' defaults can stand in for a missing value: molarity and volume units by dimension, a named carrier with its percentage (0 is a value; the percentage becomes a fraction once), no conversion from mass, formula weight or purity, absent allowances meaning "no allowance included" and absent limits "not checked". The new Assay keeps only reviewed analysis choices and starts without observations; preparation-control labels assign no 0% or 100% response, and an analysis block is written only from the reviewed settings, dated the review day. The carrier calculation tracks the named carrier from the stock, assumes the diluent contributes none of it and treats volumes as additive. Lineage is organisational: it does not make two runs comparable or replicate one another. The mapping is checked against the write-once repeat-setup-1.json (Oracles and verification); see Calc, Assay and Storage and state.