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

Engines

Every computational and rendering engine in the suite: the four third-party engines (Mol*, RDKit.js, 3Dmol.js, OpenChemLib) with how they are shipped, loaded, sandboxed and upgraded, and a catalogue of the in-house numerical kernels, the Web Workers that run them and the search indexes. The science behind each kernel is explained on the app pages; this page says where the code is, what algorithm it runs, what limits it enforces and what checks it.

Scope and conventions

  • "Engine" here means code that computes a scientific result or draws a molecule, a structure or a scene. Page components, stores and the network layer are covered by Architecture, Shared shell and Storage and state.
  • Every in-house kernel listed is pure TypeScript with relative imports and no React or DOM unless the entry says otherwise, so it runs in the browser, in a Web Worker, in node --test and in the pipeline/ scripts. That property is stated in the header comment of nearly every module and is what lets one implementation serve all four.
  • Reference values come from external tools, never from the engine under test. The oracles, fixtures, tolerances and manifests are documented in Oracles and verification; this page names the test file and oracle for each kernel and stops there.
  • Licence facts are recorded, not concluded. Questions about whether a licence permits sale are held for the General Counsel (D-0010). See Dependencies and licences.
  • The Wave 4 test files src/lib/protein/align/align.test.ts, src/lib/protein/boundary.test.ts, src/components/tools/protein-explorer/alphamissense.test.ts, substitution.test.ts, src/components/tools/molecule-workbench/policy.test.ts, series.test.ts and src/components/tools/drug-pipeline-explorer/coverage.test.ts were run singly with node --test on 11 October 2026 and passed.
  • Unit test files named below were run with node --test on 10 October 2026 for this page: src/lib/vendor.test.ts, src/lib/fit/fit.test.ts, src/lib/fit/ode.test.ts, src/lib/fit/irls.test.ts, src/lib/fit/fixtures.test.ts, src/lib/seq/tm.test.ts, src/lib/seq/structure.test.ts, src/lib/seq/digest.test.ts, src/lib/assay/qpcr/qpcr.test.ts, src/lib/dilution/fixtures.test.ts, src/lib/hosts/fixtures.test.ts, src/lib/hosts/golden.test.ts, src/components/tools/molecule-workbench/chem.test.ts and src/lib/search/catalog.test.ts all passed. Other test files were not run for this page.

Engines at a glance

Engine Kind Version Licence (package.json) Shipped as Loaded Runs in Apps
Mol* (molstar) 3D structure viewer 5.11.0 MIT Static file in public/vendor/molstar@5.11.0/ loadScript + loadStylesheet when a 3D view comes near the screen (Protein) or the reader asks for 3D (Cell, education tools) Main thread, WebGL Protein, Cell, education tools
RDKit.js (@rdkit/rdkit) Cheminformatics, WebAssembly 2026.3.6 (the package writes 2026.03.6) BSD-3-Clause Static files in public/vendor/rdkit@2026.3.6/ importScripts inside a worker on its first request Web Worker Molecule, Assay (structure drawings)
3Dmol.js (3dmol) 3D molecule viewer 2.5.5 BSD-3-Clause Static file in public/vendor/3dmol@2.5.5/ loadScript when the 3D shape panel comes within 200 px of the screen Main thread, WebGL Molecule
OpenChemLib (openchemlib) Structure editor, conformers, MMFF94 9.25.0 BSD-3-Clause Bundled by Next (dynamic import() and a worker chunk); tables in public/data/molecule-workbench/ocl-resources.json Editor on request; conformer worker when the 3D panel generates a shape Main thread (editor), Web Worker (conformers) Molecule
src/lib/fit 4PL fitting, intervals, model comparison, Z′, calibration, ODE solver FIT_VERSION 2.0.0, ODE_VERSION 1.1.0 In-house Bundled With the app Worker for batches, page for one curve Assay, Bioreactor, education
src/lib/hosts Bioreactor model, transfer, feed, rescue, scale-up MODEL_VERSION 2.0.0 In-house Bundled; preset runs precomputed at prebuild With the app Web Worker Bioreactor
src/lib/hosts/fit B2: the batch μmax estimate with every other model input fixed (admission, predictions at the exact observation times, a 33/65/129 grid in the scaled bound, Brent's bounded method per basin, a tightened refinement and a bounded Levenberg–Marquardt cross-check, the numerical rules of verification 0.9.0, the CSO's practical flags, centred-difference sensitivities); a new engine (D-0131), behind a fence: it imports only model.ts, src/lib/fit/ode.ts and src/lib/fit/*, and nothing else in src/lib/hosts imports it (fence.test.ts) BATCH_FIT_VERSION 1.0.0, schedule bioreactor-batch-fit-schedule@1.0.0 (levels rtol 1e-7, 1e-8 shipped, 1e-10 tightened, the run's absolute tolerances scaled with rtol; at most 8,000 integrations and 400,000 steps each) In-house Bundled into its own worker chunk (fit.worker.ts, marker drybench-worker:bioreactor-batch-fit) Not yet loaded by any page: the receipt surface is BR7.3 Web Worker, one job per worker (fit-client.ts: a fresh worker for every Estimate, terminated on every end; one absolute deadline from the click) Bioreactor (to come)
src/lib/seq Sequence algorithms, thermodynamics, digests, assembly, file formats per module In-house Bundled With the app Page, or worker above size thresholds Sequence, Plasmid, Calc, Protein
src/lib/protein/align Pairwise protein alignment (Gotoh affine gaps, BLOSUM62), co-optimal residue pairs ENGINE_VERSION protein-align 1.0.0 In-house; BLOSUM62 generated from NCBI's file (licence held for the General Counsel) Bundled into a worker chunk When an alignment runs Web Worker, one job per worker Protein
src/lib/protein/residue-map.ts Model/current-sequence all-optima correspondence; standard-residue exact transfer only protein.residue-map@1.0.0, standard-invariant-free-ends-v1 In-house, reuses D1 Dedicated residue-map.worker.ts chunk On a current source/sequence attempt Web Worker, one job per worker; no main-thread fallback Protein
src/lib/protein/experimental-map.ts, experimental-frame.ts F-2: experimental residue correspondence from the loaded file's own residue-level SIFTS, polymer scheme and selected-model atoms; a bounded, yielding reader of the Mol* CIF frame protein.experimental-residue-map@1.0.0, loaded-sifts-observed-standard-v1, parser mmcif-sifts-columns-v1 In-house, pure experimental-source.ts is loaded on demand by the experimental viewer after every successful load (molstar-view.tsx only imports it; readLoadedSource decides) and publishes to experimental-source-store.ts, live, once per loaded file (fileGeneration from a WeakMap on the loaded file, monotonic across viewers, unchanged by a recolour; a read finishing for a replaced or released viewer is discarded), as its own chunk (drybench-island:protein-experimental-source, budget row protein-experimental-source in scripts/size-budgets/protein-explorer.json), never the workspace chunk; no view consumes it yet After an experimental scene load succeeds Main thread, cooperative: the frame reader yields every 256 rows after 8 ms; a newer file aborts the older read Protein (F-2, in progress)
src/lib/seq/align Pairwise DNA alignment (a three-state affine-gap programme, as Biopython 1.88's PairwiseAligner (listed there as 'Gotoh (three-state)'); attribution to Gotoh 1982 not checked against the paper; 15-letter IUPAC scoring), both strands, circular one-turn mapping, seeded band, co-optimal count and listing DNA_ALIGN_VERSION dna-align 1.0.0 In-house Not yet loaded by any page; a worker is drafted (dna-align.worker.ts), the view to come (B-0) n/a Web Worker, one job per worker (drafted) Sequence (to come)
src/lib/seq/reads Sequencing-read readers: ABIF (.ab1) and FASTQ, the normalised read, end trimming (Stage B, D-0107) per module, no version constant yet In-house Bundled Not yet loaded by any surface Web Worker per job (drybench-worker:sequence-reads) Sequence (when wired)
src/lib/assay/qpcr ΔΔCq and efficiency-corrected quantification QPCR_VERSION 1.0.0 In-house Bundled With the panel Web Worker per run Assay
src/lib/units, src/lib/dilution, src/lib/preparation Units, formula weights, dilution and preparation planning per method record In-house Bundled With the app or task island Page Calc, Assay, education
src/lib/table Delimited text, plate exports, plate maps n/a In-house Bundled With the import Page Assay
src/lib/search Offline name indexes over catalogue files file hashes In-house Catalogue files in public/data/search/ On intent (useCatalogs) Page Every app, objects, literature
fuse.js Fuzzy string search 7.5.0 Apache-2.0 Bundled With the literature pages Page Literature

How the vendored engines are delivered

Mol*, RDKit.js and 3Dmol.js are not bundled by Next. They are copied as prebuilt files and loaded at run time from the site's own origin. Architecture describes the same mechanism from the build's point of view.

Copy step

scripts/vendor.mjs runs as npm run vendor, which predev and prebuild call. Its exported packages list names, for each engine, the package, the files to copy and the licence texts:

Name Package Files copied Licence text copied as LICENSE.txt
molstar molstar build/viewer/molstar.js, build/viewer/molstar.css the package's LICENSE
rdkit @rdkit/rdkit dist/RDKit_minimal.js, dist/RDKit_minimal.wasm scripts/licenses/rdkit-LICENSE.txt (the npm package ships no licence; the copy is from the Release_2026_03_6 tag of github.com/rdkit/rdkit); also scripts/licenses/inchi-LICENSE.txt as INCHI-LICENSE.txt, for the InChI 1.07.3 code compiled into the WebAssembly
3dmol 3dmol build/3Dmol-min.js, build/3Dmol-min.js.LICENSE.txt the package's LICENSE

The script reads the version from package.json dependencies, refuses anything that is not an exact numeric pin ("pin … to an exact version"), writes to public/vendor/<name>@<version>/, deletes directories for other versions of the same name, and throws if a listed file or licence text is missing. public/vendor/ is generated and gitignored.

scripts/notices.mjs (the second half of npm run vendor) walks the production dependency tree in node_modules and writes public/third-party-notices.txt with every package's licence text, using the texts scripts/lib/licence-texts.mjs records for packages that ship none (LOCAL_LICENCES: @rdkit/rdkit, @nodable/entities, server-only, client-only and the @next/* packages). OpenChemLib, fuse.js and the other bundled libraries reach the notices file this way, since a minifier strips their licence comments. The same file carries InChI's licence (compiled into RDKit.js) and the OFL 1.1 texts of the four self-hosted fonts.

Paths and versions

src/lib/vendor.ts holds vendorVersions (molstar: "5.11.0", rdkit: "2026.3.6", "3dmol": "2.5.5") and the URL map vendor (molstarJs, molstarCss, rdkitJs, rdkitWasm, threeDmolJs). src/lib/vendor.test.ts fails when any of the three differs from package.json. The version in the path makes each release a new URL, so /vendor/* is served public, max-age=31536000, immutable (suiteHeaders; see Security and network).

Loader

src/lib/load-script.ts exports loadScript(src, { timeoutMs }) and loadStylesheet(href, { timeoutMs }). Each appends one <script async> or <link rel="stylesheet"> per key per page, however many callers ask; concurrent callers share the pending promise. The default timeout is 60,000 ms. A load that errors or times out removes its element and forgets its promise, so the next call tries again. Callers may pass a longer timeout or wrap the promise in their own (Cell does, see below).

Licence check

scripts/licences.mjs (npm run check-licences) imports the same packages list and fails when a vendored package's declared licence is not on its allow-list or when its licence text is missing. It also records an exception for Python-2.0 (argparse, reached through Mol*'s command-line entry, which is not part of the vendored viewer), with the note "heldFor: General Counsel (D-0010)".

Mol*

What it is used for

The prebuilt Mol* viewer (build/viewer/molstar.js, which sets window.molstar with Viewer) draws macromolecular structures from MolViewSpec (MVS) scene descriptions. The suite never uses Mol*'s own panels: every layout and control option is turned off and the Stage frame supplies zoom, reset and screenshot controls.

Use Wrapper Scene builder
Protein: predicted (AlphaFold DB) and experimental (PDB) structures src/components/tools/protein-explorer/molstar-view.tsx (MolstarView, releaseViewers, viewerController, useViewersReady) src/components/tools/protein-explorer/mvs.ts (predictedScene, experimentalScene, withLocalFile)
Cell tour and the education tools' molecular workspaces src/components/tools/inside-a-cell/molstar-viewer.tsx (default export MolstarStage), imported with next/dynamic by src/components/tools/inside-a-cell/stage.tsx and src/apps/education/workspace.tsx src/components/tools/inside-a-cell/mvs.ts (buildMvs, cameraView, zoomedPosition)

Both wrappers type only the small part of the Mol* API they call (Viewer.create, loadMvsData(json, "mvsj"), structureInteractivity, handleResize, dispose, plugin.canvas3d.camera, plugin.canvas3d.setProps, plugin.helpers.viewportScreenshot, plugin.canvas3dContext). The MVS builders are pure modules tested under Node (mvs.test.ts in each directory) without loading Mol*.

Loading and lifecycle

  • Protein. A view starts Mol* only when its element comes within 400 px of the viewport (an IntersectionObserver with rootMargin: "400px") and hasWebGL() is true. createViewer loads the stylesheet and script, disables 16 Mol* extensions it never uses (DISABLED_EXTENSIONS), turns illumination and XR off where the build's PluginConfig.Viewport offers them, sets pdbProvider: "pdbe" and volumeStreamingDisabled: true. A viewer that has not started within 45 s reports an error and "Try again" starts a fresh one. Structure files are downloaded by the page (fetchWithTimeout, 60 s, one retry), handed to Mol* as an object URL, and reused while a scene only recolours (keepCamera when the URL is unchanged).
  • Viewer slots. Protein keeps one Mol* viewer per view name for the life of the tool (slots in molstar-view.tsx) and moves its element between mounts, because the code comment records that a new viewer per mount "cost a WebGL context and, in testing, several megabytes of heap that Mol* did not release on dispose". releaseViewers() disposes them when the tool unmounts.
  • Cell and education. The 3D view is a next/dynamic chunk loaded only after the reader asks for 3D. It loads the viewer code with a 180 s limit (VIEWER_TIMEOUT_MS, sized for a 1.5 MB download at about 70 kbit/s) and the scene list (/data/inside-a-cell/scenes.json, 30 s) in parallel, downloads each step's structure itself (so it can cancel and time it out) and passes the bytes as a blob: URL. Scene loads run one at a time (createSerialLoader in nav.ts); a scene that takes over 60 s (SCENE_TIMEOUT_MS) is treated as a stuck viewer and a fresh viewer is started. If the viewer code arrives after its time limit, the page recovers without a click. A Blender still covers the canvas until each scene is ready.
  • Context loss. Cell subscribes to canvas3dContext.contextLost and contextRestored and offers a reload. Protein does not subscribe to these events.
  • Theme. Both call syncMolstarSurface (src/components/suite/theme/viewer-surface.ts) so the canvas follows the suite theme.

Software rendering

src/components/tools/inside-a-cell/render-cost.ts reads the WebGL renderer name (the unmasked name from WEBGL_debug_renderer_info where offered) and matches swiftshader|llvmpipe|softpipe|software|basic render driver. On a match, every scene load turns off Mol*'s ambient-occlusion pass and temporal multi-sampling (SOFTWARE_RENDER_PROPS), because MVS loads reset canvas settings and those passes take seconds per frame on a CPU rasteriser. render-cost.test.ts covers the matcher. Protein does not apply this adjustment.

Size

molstar.js is 5,027,864 bytes raw and 1,437,105 bytes at gzip level 9 (measured on the installed package); molstar.css is 72,842 bytes raw. The registry's sizeNote for Protein and Cell says "about 1.5 MB".

RDKit.js

What it is used for

RDKit.js (MinimalLib, RDKit_minimal.js plus RDKit_minimal.wasm) parses SMILES, computes RDKit descriptors, InChI and InChIKey, Morgan fingerprints, substructure matches and 2D SVG depictions. The Molecule app uses all of it; the Assay app uses it only to draw structures beside potencies and to key compounds by InChIKey.

The Node build of the same package is also loaded by src/lib/chem/descriptors.ts for the local MCP server (Local MCP server); it reuses the Molecule engine's descriptor code and is not shipped to browsers.

Shared engine

The engine lives in src/components/suite/engines/rdkit/ (moved from the Molecule tool tree on 6 October 2026 so Assay imports no other app's tree):

File Role
protocol.ts Types only. Requests init, analyse, library, neighbours, series (one row of a Molecule series, since Wave 4), depict (sizes main, thumb, table); responses carry ok and, on failure, a code: invalid, too-large, network, engine, crashed, or incompatible (the shipped library's fingerprint policy, RDKit or descriptor version differs from the engine's). Depiction is { svg, svgDark, inchiKey? }.
rdkit.worker.ts The worker. On the first request it calls importScripts(vendor.rdkitJs) and initRDKitModule({ locateFile: () => vendor.rdkitWasm }), sets prefer_coordgen(true) and compiles the query patterns (compileAll). A download failure is network; a failure before RDKit started is engine. When it loads the library it runs libraryCompatibility and compares the library's rdkit with rdkit.version(), throwing IncompatibleLibraryError (code incompatible) on any difference. Any other exception after start (a WebAssembly trap, for example) drops the module and the library, posts crashed and calls close(), because the module may be left in a broken state.
client.ts RdkitClient on the main thread: request ids, per-request timeouts, caches, and an EngineState (idle, starting, ready, restarting, failed) a status chip subscribes to. A crashed response or a worker onerror fails everything in flight and the next request starts a fresh worker. A timeout after RDKit has loaded terminates the worker (the calculation cannot be interrupted); a timeout during the download leaves the worker running so a retry reuses it.

Client timeouts: init and analyse 120 s, depict 30 s, library, neighbours and series 150 s. Caches: up to 40 analyses keyed by SMILES and up to 400 depictions keyed by size and SMILES, oldest evicted first; failed promises are removed from the cache.

The chemistry itself stays in the Molecule tree and is imported by the worker: src/components/tools/molecule-workbench/chem.ts (parsing, size checks, descriptors, fragment selection, InChIKey, depiction) and engine.ts (analyseSmiles, depictSmiles, prepareLibrary, neighboursOf, describeSeriesRow). The same two modules run in Node in pipeline/molecule-workbench/ and in the unit tests against the npm package, so the shipped data and the browser give the same numbers.

When it loads

The page asks the worker for nothing until a structure is needed. Molecule's opening example (Imatinib) is precomputed by pipeline/molecule-workbench/build-default-example.mts with the same engine.ts; e2e/molecule-workbench/workspace.spec.ts asserts that opening it makes no request to /vendor/rdkit. Assay creates one RdkitClient per page (rdkitClient() in src/components/tools/assay-analyser/panels/structure-thumb.tsx) and calls depict only when a SMILES column is present. Molecule creates its client in src/components/tools/molecule-workbench/use-workbench.tsx.

The approved-drug library (/data/molecule-workbench/approved-drugs.json) is fetched by the page and again by the worker with cache: "force-cache" (45 s timeout), so the worker normally reads the HTTP cache. Fingerprints are computed in the worker rather than shipped; pipeline/molecule-workbench/README.md records 0.7 s in Node and 1.6 to 2.5 s in headless Chromium for 2,197 drugs.

Input limits

src/components/tools/molecule-workbench/limits.ts: SMILES up to 2,000 characters; at most 500 heavy atoms (MAX_HEAVY_ATOMS), because the comment records that a 600-carbon chain traps RDKit.js with "memory access out of bounds" and leaves the module unusable; the drawing editor takes at most 150 atoms. checkSize counts heavy atoms from the SMILES text before RDKit sees it and throws TooLargeError (too-large).

Size

RDKit_minimal.wasm is 7,333,095 bytes raw and 2,386,796 bytes at gzip 9; RDKit_minimal.js is 105,843 raw and 28,365 gzip. The registry's sizeNote says "about 2.4 MB" for both Molecule and Assay.

Two ported RDKit components

RDKit.js has no QED and no PAINS filter catalogue, so two pieces of RDKit are reproduced in the source tree:

  • src/components/tools/molecule-workbench/qed.ts is a TypeScript port of rdkit/Chem/QED.py at Release_2026_03_6; it reproduces the Novartis BSD-3-Clause notice at the top of the file and copies the SMARTS, ADS parameters and weights verbatim.
  • src/components/tools/molecule-workbench/pains-data.ts is generated by pipeline/molecule-workbench/rdkit_reference.py pains from RDKit's FilterCatalog pains_*.in files at the same tag (painsSource = "Release_2026_03_6"), with explicit hydrogens merged as FilterCatalog does.

These are the only places in the repository found to carry code or data ported from a third-party engine. Whether the notices satisfy the licence is held for the General Counsel.

3Dmol.js

3Dmol.js draws the single conformer the Molecule app's 3D shape panel generates. The wrapper is src/components/tools/molecule-workbench/three-dmol.ts; the panel is src/apps/molecule-workbench/panels/shape-panel.tsx.

  • Loading. The panel watches itself with an IntersectionObserver (rootMargin: "200px"). When it comes near, it calls loadScript(vendor.threeDmolJs) (an 8 s "slow" note, then the loader's 60 s timeout) and reads window.$3Dmol.
  • One viewer per visit. The code comment records that 3Dmol.js 2.5.5 cannot dispose a viewer: its constructor adds window and body listeners, a ResizeObserver and an IntersectionObserver that cannot be removed. attachViewer therefore creates one viewer on a detached div and moves it into the stage on mount; detachViewer stops spinning, removes all models, renders the empty scene and takes the div out.
  • Drawing. The panel calls addModel(molfile, "sdf"), sets a stick or sphere style with the Jmol colour scheme, optionally hides hydrogens and calls zoomTo(). Rotation is off by default and unavailable under reduced motion.
  • Size. 3Dmol-min.js is 537,792 bytes raw and 156,496 bytes at gzip 9.

OpenChemLib

OpenChemLib (openchemlib 9.25.0, BSD-3-Clause) is bundled by Next rather than vendored. It has two uses, both in the Molecule app.

Structure editor

src/components/tools/molecule-workbench/structure-editor.tsx imports openchemlib dynamically and creates a CanvasEditor only after the reader presses "Load the drawing editor" or presses a mouse or pen in the box (touch requires the button, so a swipe scrolls). The comment records about 340 KB compressed and about half a second of main-thread work. Drawn molecules are reported as isomeric SMILES after a 450 ms debounce. The editor's drawing canvas is removed from the Tab order because it has no keyboard function; the SMILES field is the keyboard route.

Conformer worker

src/components/tools/molecule-workbench/conformer.worker.ts, driven by ConformerClient in clients.ts:

  1. Fetches /data/molecule-workbench/ocl-resources.json once (45 s timeout) and registers it with Resources.register, skipping keys that start with _. The file is written by pipeline/molecule-workbench/copy-ocl-resources.mts: the 22 torsion, bond-length and MMFF94 tables from OpenChemLib's resources.json, with _source, _version, _licence and _notice keys (953,937 bytes raw). The script refuses to run if the count is not 22.
  2. Parses the SMILES with Molecule.fromSmiles; refuses metals that need coordination chemistry (isComplexMetal) before stripping small fragments, then strips them, refuses more than 100 heavy atoms (MAX_3D_ATOMS), refuses elements outside ORGANIC_ELEMENTS, and refuses a structure with no carbon.
  3. Counts stereocentres left unspecified.
  4. Builds one conformer with new ConformerGenerator(42).getOneConformerAsMolecule(mol) (seeded, so a molecule always gives the same shape).
  5. Minimises with ForceFieldMMFF94(…, MMFF94SPLUS) and minimise({ maxIts: 4000 }), reporting converged, stopped or skipped (MMFF94 has no parameters for some elements, boron for example; the torsion-library conformer is kept).

ConformerClient keeps one request in flight (a new one cancels the old by terminating the worker), caches 20 results by SMILES, and accepts a per-request timeout. Since Wave 4 each result carries OpenChemLib's runtime version, which the conformer export names. The panel uses 90 s, and 240 s for one retry with more time; molecules over 60 heavy atoms (CONFIRM_3D_ATOMS) wait for the reader to ask.

Other runtime libraries

These are small and are not engines in the same sense, but they run code over user data:

Package Version Licence Where Use
fuse.js 7.5.0 Apache-2.0 src/lib/literature/search/concepts.ts, saved-papers.ts (client) Spelling-tolerant subject suggestions and saved-paper filtering; see Search catalogues and indexes
uqr 0.1.3 MIT src/components/tools/lab-calculators/panels/label-dialog.tsx, loaded by dynamic import() from src/apps/lab-calculators/views/bench-sheet.tsx QR codes on printed labels
fast-xml-parser 5.11.2 MIT src/lib/literature/fulltext.ts (server routes /api/literature/fulltext and /api/literature/extract); src/lib/grid/pzfx.ts (Assay's Import, in the browser, on demand) Europe PMC full-text XML; the .pzfx project file
marked 18.0.14 MIT src/lib/technical-docs/render.ts (server) This documentation set
@floating-ui/dom 1.8.0 MIT src/components/suite/overlay/floating.ts Popover positioning

Content Security Policy allowances

The policy is generated per app from the registry by content/csp.ts; the full policy is on Security and network and the registry fields on Registry and content. The engine-specific parts:

Allowance Granted by Apps Why
'wasm-unsafe-eval' in script-src dataPolicy.wasm: true Molecule (content/apps/molecule-workbench.ts), Assay (content/apps/assay-analyser.ts) RDKit.js compiles WebAssembly
'unsafe-eval' in script-src dataPolicy.cspAllowances.unsafeEval (a reason string) Protein, Cell; the education tools inherit Cell's policy The reason string: "The Mol* 5.11.0 viewer starts a WebAssembly module bundled inside it whose bindings are compiled when it loads, so the viewer cannot start without it."
data: in connect-src dataPolicy.cspAllowances.dataConnect Protein, Cell, education tools "Mol* reads that bundled module from a data: address inside its own script."
worker-src 'self' blob: every workspace policy all Same-origin worker scripts; blob: is allowed in case Mol* creates a worker from a blob. No suite code creates a blob: worker (searched for this page).
blob: in connect-src every workspace policy all Structure bytes handed to Mol* as object URLs

Object and study pages get the union of the policies of the apps they show (objectPageHeaderRules), so a page that can show Molecule and Protein views carries both allowances. In development 'unsafe-eval' is added everywhere for React's tooling.

Worker scripts themselves are served from /_next/static/ with no CSP header: content/csp.test.ts asserts that /_next/static/chunks/main.js and /vendor/molstar@5.11.0/molstar.js get no policy, and e2e/smoke/apps.spec.ts ("scripts under /_next/static carry no security policy of their own (foundation decision C11)") checks the live headers. Under CSP Level 3 a worker created from a URL takes its policy from its own response, so the workers run without a policy of their own; the test comment says a worker is held to its app's hosts "by the app's network code instead". This differs from design system §11.5 (docs/design/suite-design-system.md, the "WebAssembly and workers" paragraph), which says the same CSP is applied to /_next/static/:path* and that a Playwright check asserts it on the worker script response. The code and tests follow C11; the design text is out of date. Not verified in a browser for this page.

WebGL detection and fallbacks

src/lib/webgl.ts exports hasWebGL(): it creates a canvas, asks for webgl2 then webgl, releases the test context with WEBGL_lose_context (browsers cap live contexts), caches the answer for the page and returns false on any exception.

Viewer Check Fallback
Protein (Mol*) hasWebGL() before a viewer starts; status no-webgl A message from content/apps/protein-explorer.workspace.ts (noWebgl): "This browser cannot show 3D graphics (WebGL is unavailable). The tracks and tables still work."
Cell and education (Mol*) hasWebGL() once the reader asks for 3D The noWebgl message from content/apps/inside-a-cell.workspace.ts, announced through the host's live region; the Blender still remains
Molecule 3D shape (3Dmol.js) hasWebGL() on mount An error plate with the text hard-coded in shape-panel.tsx, not retryable; the 2D depiction and descriptors are unaffected

Software rasterisers are detected and handled for Cell only (Mol*). The browser tests run Chromium with --use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader (playwright.config.ts) so the viewers start headless; e2e/inside-a-cell/tour.spec.ts skips one viewer-state test on the phone project because Mol* on software WebGL is slow there. e2e/smoke/three-d-theme.spec.ts replaces molstar.js and 3Dmol-min.js with stubs to test theme handling. No browser test was found that exercises the no-WebGL fallback path (searched e2e/ for the fallback copy and for "WebGL").

Performance budgets and tests

  • Bundle budgets (scripts/size-check.mjs, npm run size-check, budgets in scripts/size-budgets/*.json) measure Next's build output: route first-load JavaScript, CSS, lazy client modules and named dynamic chunks. The vendored engines are static files outside the build and have no size budget; their sizes are stated only in each registry entry's flags.sizeNote. OpenChemLib is inside the Next build but no budget names its chunk. The Bioreactor worker has a chunk budget, bioreactor-worker, 30 KB gzip, found by the marker string drybench-worker:bioreactor-simulator that sim.worker.ts keeps at run time; the protein alignment worker has protein-align-worker, 9.2 KB (8.0 measured on 11 October 2026), by drybench-worker:protein-align. The named-task draft writer has work-draft-worker in suite.json, 27.3 KB (23.7 measured on 12 October 2026), by drybench-worker:work-draft. The B2 fit worker (fit.worker.ts, marker drybench-worker:bioreactor-batch-fit) has no row yet: no page loads it until the receipt surface (BR7.3), so the build has no chunk to measure; its row is set from the Fast chain's first measurement + 15 %, capped at 45 KB, and its reporting spec e2e/perf/bioreactor-fit-worker.spec.ts joins the worker-gate matrix then (the CTO's Stage H memo §3; REL-578).
  • Timing gates (see Build, test and CI): e2e/perf/two-second.spec.ts (Calc and Sequence) runs under npm run test:perf. Assay's IC50 gate runs under npm run test:perf:report without gating (D-0049). e2e/perf/protein-align.spec.ts (Wave 4, D-0055) runs under npm run test:perf:worker: EGFR against a synthetic 8,264-residue sequence (9,999,440 cells), the worker's own reported time, median of three, a load average above the core count failing the test as inconclusive. Protein-align: target 2,000 ms; measured 622.9 ms median, 4-core host, 11 October 2026; not measured on CI. e2e/perf/bioreactor-worker.spec.ts uses the same config: the median of five adjusted figures at most 2,000 ms for a first change and 1,000 ms with the worker running. Chrome DevTools CPU throttling does not reach a worker, so that test adds three times the worker's own reported time (ms in the run message) and its start-up. Under D-0047 both worker gates run weekly and on demand in separate scheduled.yml matrix runners (CTO, 10 and 11 October 2026); a release needs a quiet-host pass within seven days.
  • Engine-level limits that bound worst-case time are listed with each worker in Keeping heavy work off the main thread.

Upgrading a third-party engine

There is no single written upgrade procedure in the repository. The steps below are assembled from what the code checks and what pipeline/molecule-workbench/README.md says; anything not enforced by a test is marked.

Common steps for the vendored engines

  1. Change the exact pin in package.json and run npm install.
  2. Change vendorVersions in src/lib/vendor.ts. src/lib/vendor.test.ts fails until they agree.
  3. Run npm run vendor. If the package moved or renamed a build file, scripts/vendor.mjs throws "missing"; update packages.
  4. Run npm run check-licences and confirm the licence and its text.
  5. Update hard-coded version strings that no test checks (searched for this page): content/tools/protein-explorer.ts and content/tools/inside-a-cell.ts ("Mol* 5.11.0"), the cspAllowances reason strings in content/apps/protein-explorer.ts and content/apps/inside-a-cell.ts, content/tools/molecule-workbench.ts ("3Dmol.js 2.5.5", "OpenChemLib JS 9.25.0"), content/csp.test.ts (a sample /vendor/molstar@5.11.0/ path), and the comment in three-dmol.ts about disposal in 2.5.5.
  6. Run the unit tests, the browser tests (the viewers run on SwiftShader) and npm run test:perf.

Mol*

  • Re-check that the viewer still needs 'unsafe-eval' and data: connections; the reason strings name the version.
  • Re-check the typed API surface both wrappers use (listed under Mol*); the wrappers reach into plugin.canvas3d, plugin.managers.structure, plugin.helpers.viewportScreenshot and lib.plugin.PluginConfig.Viewport, which are internal to Mol* and not a stable public API.
  • Re-check DISABLED_EXTENSIONS names, the MVS format the builders write (mvs.ts in both apps, tested in Node without Mol*), and the software-render props, which loadMvsData resets on every load.
  • Risk: the heap behaviour that led to the viewer slots, and the camera behaviour that led Cell to set camera states with zero duration, were observed on this version only.

RDKit.js

RDKit's version is load-bearing in tests and data, so an upgrade is a data release as well as a code change:

  1. Replace scripts/licenses/rdkit-LICENSE.txt with the text from the new release tag.
  2. Port any change in rdkit/Chem/QED.py at the new tag into qed.ts, and regenerate pains-data.ts with rdkit_reference.py pains, which fetches the PAINS definitions at the tag matching the installed Python RDKit and refuses another version.
  3. Install the same release of Python RDKit in a virtual environment outside the repository and regenerate the reference fixture (rdkit_reference.py reference). chem.test.ts asserts that the installed RDKit.js reports the fixture's version, default-example.test.ts that the shipped opening example was computed with it, src/lib/seq/peptide.test.ts that the peptide SMILES fixture is RDKit "2026.03.6", and src/apps/education/experiments/applied/models.test.ts the same for the education molecules; each fails until regenerated.
  4. Rebuild the library, the PCA map and the opening example (build-library.mts, rebuild-map.mts, build-default-example.mts) and run the full-library comparison (rdkit_reference.py library then verify-library.mts). The last recorded comparison (10 October 2026) matched all 2,197 molecules; see Oracles and verification and Data pipeline.
  5. Update RDKIT_VERSION in content/apps/molecule-workbench.methods.ts ("2026.03.6"), which feeds export provenance and is not compared with the installed package by any test; and src/lib/units/fixtures/formula-mass.json, which records the RDKit used to derive formulas.
  6. Re-measure MAX_HEAVY_ATOMS: the 500-atom limit is an empirical bound on the WebAssembly stack for this build.

3Dmol.js

Re-check whether the new release can dispose a viewer (if so, the shared-viewer workaround in three-dmol.ts can go), and the Viewer3Dmol methods the wrapper types.

OpenChemLib

Rerun pipeline/molecule-workbench/copy-ocl-resources.mts, which writes _version and refuses a release whose table count is not 22. No test compares _version in ocl-resources.json with the installed package, so a stale table file would load silently. Update the literal "9.25.0" in src/apps/molecule-workbench/exports.ts (export provenance) and content/tools/molecule-workbench.ts. Seeded conformers may change shape between releases.

Keeping heavy work off the main thread

Every worker follows the same rule, stated in several headers as the CTO's: a worker computes and never fetches, except the RDKit and conformer workers, which download their engine and tables. Workers are created with new Worker(new URL("./x.worker.ts", import.meta.url)), which Next bundles as same-origin scripts. The RDKit worker is a classic worker (it calls importScripts); the primer-design and sequence-review workers are created with { type: "module" }.

Worker Client Started Work Limits and cancellation
src/components/suite/engines/rdkit/rdkit.worker.ts RdkitClient (client.ts) First chemistry request RDKit.js analysis, depiction, library fingerprints, neighbours, series rows Timeouts 30 to 150 s; crash or timeout replaces the worker; 2,000 characters, 500 heavy atoms; refuses a library computed under another fingerprint policy, RDKit or descriptor version (incompatible)
src/components/tools/molecule-workbench/conformer.worker.ts ConformerClient (clients.ts) 3D panel near the screen, 300 ms after a molecule is chosen OpenChemLib conformer and MMFF94s+ 90 s (240 s retry); new request terminates the old; 100 heavy atoms; confirm above 60
src/components/tools/assay-analyser/fit.worker.ts FitClient (fit-client.ts) A batch of two or more curves analyseCurve in chunks of 25 (FIT_CHUNK), streaming results and progress At most 400 curves (FIT_CAP); cancel is read between chunks; one curve is fitted on the page
src/components/tools/assay-analyser/qpcr.worker.ts qpcr-panel.tsx Each calculation (a new worker each time) calculateQpcr Terminated after one answer or on cancel; no timeout; result accepted only if job id and input hash match
src/components/tools/assay-analyser/calibration.worker.ts calibration-panel.tsx Each fit (a new worker each time) fitCalibration As qPCR
src/components/tools/bioreactor-simulator/sim.worker.ts SimClient (sim-client.ts) Warmed on a lever's first focus or press (warm()), never on page load runJob (sim-job.ts): run, comparison run, rescue Latest request wins, one in flight; 5 s per stage (SIM_DEADLINE_MS), one retry at 10 s; solver step cap 40,000; horizon 1 to 240 h; requests in any other shape refused (readSimRequest)
src/components/tools/protein-explorer/parse.worker.ts parse-off-thread.ts Variant list or AlphaMissense CSV download JSON and CSV parsing, with the AlphaMissense reference-letter check; bytes and result arrays transferred, not copied Falls back to the same parsers on the page when a worker cannot start; up to 20,000 variant records, confirm above 8,000 (variants.ts)
src/components/tools/protein-explorer/align.worker.ts runAlignment (align-client.ts) Each alignment (a new worker each time) alignPair: checks, forward pass, traceback, co-optimal pairs and count, report; marker drybench-worker:protein-align 10,000,000 cells, refused before any allocation and re-checked in the worker; terminated on answer, error, cancel or a new request; result discarded unless its echoed key matches; no timeout and no main-thread fallback
src/components/tools/sequence-toolkit/dna-align.worker.ts (branch claude/sequence-dna-aligner, drafted; no page imports it yet) runDnaAlignment (dna-align-client.ts) Each alignment (a new worker each time) answer (dna-align-handler.ts): reads the request (dna-align-protocol.ts), runs alignDna from the pure engine, echoes the key (each input's length and FNV-1a hash, topology, mode, options) and the worker's own time; marker drybench-worker:sequence-dna-align The engine's caps and cell budget; terminated on answer, error, cancel or a 30 s timeout; a result whose key no longer matches is refused as stale; no main-thread fallback. No size budget until the view imports it (a chunk no page loads is not built); its perf gate (the CTO's 4,000 ms at the cell limit) needs that view
src/components/tools/sequence-toolkit/seq.worker.ts SeqWorkerClient (worker-client.ts), useScan Inputs of 100,000 letters or more (INLINE_LIMIT) Six-frame translation, ORFs, motif search, binding sites 1,000,000 letters (MAX_INPUT), checked on the page and in the worker; 60 s timeout; cancel terminates
src/components/tools/sequence-toolkit/primer-design.worker.ts primer-design-panel.tsx Each design run designPrimers 50,000-base template; terminated on finish or cancel; no timeout
src/components/tools/sequence-toolkit/sequence-review.worker.ts sequence-revision-panel.tsx Each comparison compareSequenceRevisions Edit traceback bounded to 1M cells; no timeout
src/components/tools/plasmid-map/plasmid.worker.ts PlasmidWorkerClient (worker-client.ts) A sequence is open Annotation, enzyme sites with methylation, primer sites, digests, the diagnostic digest picker; reading a SnapGene .dna file from its transferred bytes (also from the .dna task island, through a one-shot worker in dna-reader.ts) 50,000 bases (SEQUENCE_CAP), on the page and in the worker; data handed over once per release; 60 s default timeout (.dna 30 s); cancel terminates; 5 MB file cap before reading
src/lib/workspace/work-draft.worker.ts writeWorkDraftOffThread (work-draft-writer.ts) Warmed when a named task opens; used once it has opened IndexedDB and said so Writes a named task's draft (its envelope and the payload's canonical JSON text, counted against the 4 MiB cap) to the meta store under the scope's physical key, through scopeDatabase Never fetches; until it is ready, with the memory database, or after it fails, the page writes the draft itself; a refusal over the cap comes back as WorkDraftTooLarge (REL-35, Storage and state)

Other measures that keep the page responsive:

  • Precomputation. The Molecule opening example is computed in the pipeline. The Bioreactor preset pages draw public/data/bioreactor-simulator/preset-runs.json, written at prebuild by scripts/prebuild/bioreactor-simulator-preset-runs.mjs from buildPresetRuns() (200 points per run plus event points, six significant figures, the closure residuals first rounded to an absolute 1e-9 so the bytes are machine-independent, REL-09); preset-runs.test.ts recomputes and compares, and the pages never import the model. The pages read the file from disk at build through readFixture (src/lib/fixtures/read.ts), not by import (MNT-26).
  • Small islands. src/lib/fit/curve.ts holds the 4PL formula, pIC50 and molar formatting so a chart or task island draws stored estimates without loading the fitter. src/lib/seq/features/genbank.ts, genbank-write.ts and extract.ts are split so a page that only reads a record carries neither the writer nor the extractor.
  • Lazy search structures. Name indexes build their search rows on the first search and their identity maps on the first resolve (Search catalogues and indexes).

Work that stays on the main thread: single-curve fits and their profile intervals, oligo Tm and hairpin and dimer free energies (oligos up to 100 nt), Gibson, Golden Gate and restriction-cloning assembly, CRISPR guide search, dilution and preparation planning, plate parsing, catalogue JSON parsing, and the education models.

In-house engines

Each table row gives the module, what it computes and how, its numerical behaviour, and its tests and oracle. The science, the rulings behind each choice and the user-facing wording are on the app page linked under each heading.

Dose-response fitting and statistics

App page: Assay. Module index: src/lib/fit/index.ts, FIT_VERSION 2.0.0. Method records (id, name, implementation file, version, citation) are in src/lib/fit/methods.ts, which has no imports so chips and task pages can cite a method without loading the engine.

Module Purpose and algorithm Numerical properties Tests and oracle
curve.ts fourPL(p, x): y = Bottom + (Top − Bottom) / (1 + 10^((logIC50 − x)·Hill)), x = log10 of the concentration in mol/L; formatMolarLog; pIC50 Falling curves have Hill < 0 via fit.test.ts
lm.ts Bounded Levenberg–Marquardt with an analytic Jacobian, written from Levenberg 1944 and Marquardt 1963. Damping on the diagonal of JᵀJ (floored at 1e-12 of the largest diagonal), trial steps clamped to the box and accepted only if the objective falls; λ ÷ 3 on success, × 4 on failure, up to 40 attempts per iteration Defaults: 1,000 iterations, λ₀ 1e-3, scaled projected-gradient tolerance 1e-10 (the 4PL passes 1e-6). Converged only on the projected-gradient or objective test; a stalled step is not convergence. Deterministic fit.test.ts
linalg.ts JᵀJ, Jᵀr, Gaussian elimination with partial pivoting and inversion for at most four unknowns Returns null on a singular system via fit.test.ts
four-pl.ts fitFourPL(points, settings). Any parameter may be fixed. Deterministic starts from the data (extreme means, interpolated half-response crossing, Hill ±1) plus alternate Hill magnitudes 0.5, 1, 2, 4 in both directions; the best converged run wins. Search box: plateaus within 20 × the response span, logIC50 within 5 log units of the tested range, Hill within ±50. Detects fits with no finite minimum: a step between two tested concentrations (bestStep), Hill on the box face, or a parameter that keeps falling when the box is widened (beyond-range, plateau-runaway). 1/Y² weighting is relative IRLS: an unweighted fit, then weights 1/f² frozen per solve, up to 100 iterations until the relative change is below 1e-8 Refusals: no data, non-finite input, numerically flat responses (range ≤ 64 ε × scale), all parameters fixed, non-positive responses under 1/Y², not converged, unidentifiable (column-scaled Gram inverse diagonal above 1e10 or invalid). Top is the higher plateau when both are free (canonical) fit.test.ts, science.test.ts, irls.test.ts (independent SciPy relative IRLS, pipeline/assay-analyser/relative-irls-reference.py), fixtures.test.ts (R nls and drc 4.0-0 via pipeline/assay-analyser/reference.R): logIC50 within 0.00043 log10, Hill within 0.1 percent, plateaus within 0.1 percent of the span, objective within 1e-6 relative
profile.ts Generic one-at-a-time profile extra-sum-of-squares bounds over any bounded LmProblem (Bates and Watts 1988, ch. 6), written for the kinetics intervals: a parameter-fix mask (maskedProblem), an outward search in steps of 0.02, 0.04, 0.08, … in the parameter's own units, the last clamped to the box, and the Illinois method between the last step below and the first at or above the threshold RSS_min·(1 + F/df) (profileThreshold). An optional closed-form refit replaces the masked LM for one held parameter Root to 1e-10. Side states crossing, search-limit (refitted-at-box, profiled-at-box) and refit-unresolved; an unresolved side carries no number. Not used by the 4PL, which keeps ci.ts src/lib/kinetics/michaelis-menten-profile-oracle.test.ts (SciPy direct profile, 1e-3 ln units)
ci.ts Wald interval on logIC50 from (JᵀJ)⁻¹s² and t(0.975, n − p); profile extra-sum-of-squares interval (the default): the objective reaches minimum × (1 + F(0.95; 1, n − p)/(n − p)), bracketed outward from 0.05 log10 steps growing × 1.5 to 0.5, within 10 log10 units, then bisection to 1e-7 Both withheld for weighted fits and for zero residual variance. Unreached bounds carry search-limit or refit-unresolved, never "infinite" fixtures.test.ts: Wald within 0.0022 log10, profile within 0.0043, unbounded flags exact
stats.ts Lanczos log-gamma (g = 7, nine terms), regularised incomplete beta by continued fraction (modified Lentz), F upper tail, t CDF, t quantile by bisection, F(p; 1, df) as t², mean, sample SD About 1e-15 relative for log-gamma (comment); t quantile about 1e-13 fit.test.ts against known values
compare.ts Extra-sum-of-squares F-test for nested fits (α 0.05) and AICc (K = parameters + 1) with Akaike weights Comment states both are approximate for nonlinear models fit.test.ts, fixtures.test.ts (nested F-tests on C01, C07, C04, C10)
rules.ts The display rules (i) to (vi): fewer than four concentrations; flat-line F-test p ≥ 0.05; IC50 not bracketed; plateau not bracketed beyond the bend point IC50 × 4.6805^(±1/ Hill ); absolute IC50 not bracketed; profile wider than two log10 units
response-evidence.ts Two-sided permutation test of dose-response ordering (Spearman rank covariance), permutations within plate blocks Exhaustive up to eight observations, else 1,999 fixed-seed Monte Carlo permutations with the observed ordering counted science.test.ts
analyse.ts analyseCurve: refusal, fit, intervals, rules, absolute IC50, R² (unweighted only, never a gate), Hill observation (> 1.5 steeper, < 0.67 shallower), the curve sampled at 61 points across the range ± 0.5 log The unit the fit worker runs fixtures.test.ts, src/components/tools/assay-analyser/assay.test.ts
zprime.ts Z′ = 1 − 3(σpos + σneg)/ μpos − μneg with n − 1 SDs
normalise.ts Percent of span between controls, raw, percent of control, percent inhibition, each with its formula text Refused without the needed controls or with zero span fit.test.ts, fixtures.test.ts (MTT and PRISM plates)

Calibration and qPCR

App page: Assay.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/fit/calibration.ts, calibration-model.ts Standard curves: per-concentration mean responses after optional mean-blank subtraction; linear least squares on scaled values (at least three standards) or a 4PL through fitFourPL (at least five, positive concentrations); unknowns inverted analytically, multiplied by their dilution factor; recovery of each standard No extrapolation: unknowns outside the fitted response range are below-range or above-range; no inverse confidence intervals. 1/Y² only for the 4PL. Up to 768 readings. CALIBRATION_VERSION 1.0.0 calibration.test.ts, calibration-oracle.test.ts (SciPy fixture fixtures/calibration-scipy.json)
src/lib/assay/qpcr/quantity.ts, model.ts calculateQpcr: technical replicates averaged per run, sample and assay; log2 quantity = −mean Cq × log2(E) with E = 2 (ΔΔCq) or the declared efficiency; target minus the mean of the reference log2 quantities (the geometric mean in linear terms); minus the mean of the declared calibrators in the same run; fold = 2^log; group means of log2 fold changes Works in log space and keeps finite log2 values when the fold overflows. Refuses unconfirmed biological ids, a missing spread limit, missing assays or efficiencies, duplicates, inconsistent conditions, and over 16,384 sample-target pairs. Technical spread is flagged, never auto-excluded. QPCR_VERSION 1.0.0, 4,096 rows, 2 MiB text qpcr.test.ts; src/lib/assay/qpcr/oracle.py computes Pfaffl ratios with Python Decimal at 60 digits

Enzyme kinetics, growth and thermal shift

App page: Assay. Each directory keeps the engine, the stored model with its strict readers, a compute.ts that joins them and a methods.ts with no imports, so the workspace frame and the projections carry no engine. All fit on the shared lm.ts.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/kinetics/michaelis-menten.ts v = Vmax·S/(Km + S) by OLS in (ln Vmax, ln Km), analytic Jacobian, three starts (Km₀ at the lower-median, lowest and highest level), lowest RSS; Km–Vmax correlation from (JᵀJ)⁻¹; kcatFrom; profileMichaelisMenten through profile.ts, with Vmax given in closed form when Km is held Box Km [S_min/1000, 1000·S_max], Vmax [max|v|/10⁶, 1000·max|v|]; 2,000 iterations, gradient tolerance 1e-8 (1e-6 in profile refits); a solution within 1e-4 of the box is bound; four distinct positive levels required. Method 1.1.0 michaelis-menten-oracle.test.ts (SciPy curve_fit trf: Km, Vmax 1e-5, RSS 1e-6, correlation 1e-4), michaelis-menten-profile-oracle.test.ts
src/lib/kinetics/inhibition.ts Global OLS of the competitive, uncompetitive, pure noncompetitive or mixed model with Vmax and Km shared; every parameter a log; analytic Jacobian; nine starts Ki, Ki′ box [I_min/1000, 1000·I_max]; 500 iterations; bound contact checked before termination and withholds the whole fit; failed only at the iteration cap; correlation matrix by invert (NaN off the diagonal if singular). Runs in inhibition.worker.ts, which the panel terminates on cancel or change. Method 1.0.0 inhibition-oracle.test.ts (SciPy least_squares trf, nine starts: constants 1e-5, RSS 1e-6, correlations 1e-4)
src/lib/kinetics/ki.ts Ki = IC50/(1 + [S]/Km), competitive only [S] and Km must share a dimension; no conversion between mass and molar. Method 1.0.0 ki-oracle.test.ts (Python fractions, 1e-12)
src/lib/growth/window.ts μ as the OLS slope of ln(y − mean blank) on time inside a typed window, doubling time ln 2/μ for μ > 0 Closed form; withheld for a non-positive corrected reading in the window or fewer than three distinct times. Method 1.0.0 window-oracle.test.ts (exact rationals and SciPy linregress, 1e-9)
src/lib/thermal/apparent-tm.ts Two apparent Tms per melt: the midpoint of a four-parameter sigmoid in (F_lo, F_hi, Tm, ln k) by OLS from three starts, and the first maximum of the signed central difference (no smoothing) k box [median step/100, window span/2]; 2,000 iterations; nine ordered refusals (temperatures, points < 10, failed, no transition, two transitions, sign, k at its box, edge, unbracketed); a disagreement flag above max(1 °C, two median steps). Method 1.0.0 apparent-tm-oracle.test.ts (SciPy curve_fit: Tm 1e-3 °C, ln k 1e-4; derivative exact)

ODE solver

App page: Bioreactor. Module: src/lib/fit/ode.ts, ODE_VERSION 1.1.0, no imports.

  • Method. The modified Rosenbrock (W-) formula of Shampine and Reichelt (SIAM J Sci Comput 1997;18:1–22), the method of MATLAB's ode23s, on the second-order W-method of Steihaug and Wolfbrandt. With W = I − h·d·J and d = 1/(2 + √2) it computes three stages, advances with the second-order formula, estimates the error as h/6·(k1 − 2k2 + k3) and holds |eᵢ| ≤ rtol·|yᵢ| + atolᵢ per component. The header records that no MATLAB, Octave or SciPy code was read. With the exact Jacobian the formula is L-stable.
  • Interface. rosenbrock(system, t0, y0, t1, times, options); OdeSystem takes f, an optional analytic jacobian (else forward differences), optional dfdt, an optional regimeOf for right-hand sides that switch, and an optional watch. LU factorisation with partial pivoting (luFactor, luSolve) is in the module.
  • Switches and events. When a step ends in another regime the switch is located by bisection on the continuous extension, the step is retaken to end on it and integration continues under the new right-hand side. Watched functions are sampled at the middle and end of every step and at the vertex of the quadratic through the samples, and each sign change is located by bisection to 1e-9 of the step. Watching never changes the step sequence.
  • Defaults. rtol 1e-6, atol 1e-9, 50,000 steps; a run that needs more throws OdeError with reason step-cap (also step-underflow, singular).
  • Tests. ode.test.ts: LU against LAPACK and NumPy cases, the stability function at hλ = −1 and −10⁶, order of convergence, the step cap, event location including a dip between samples, and why fixed-step RK4 (rk4Fixed, kept for this test only) diverges beyond |hλ| ≈ 2.785. dormand-prince.test-util.ts is a Dormand–Prince 5(4) integrator kept as a second route for tests only.

Bioreactor model

App page: Bioreactor. Directory src/lib/hosts/ (hosts is the host-cell model, not network hosts). MODEL_VERSION 2.0.0, MODEL_ID "hosts-model-2"; every run, link and export names the version, and a change that moves a preset's curve bumps the preset's version.

Module Purpose and algorithm Numerical properties Tests and oracle
model.ts Unstructured single-substrate model in batch, fed-batch or chemostat mode: states X, S, P, V and dissolved oxygen C, plus cumulative CO₂, O₂, fed substrate and washed-out carbon and electrons (ten states), so every run reports how closely its carbon and electron balances close. Time events split the run into segments (feed start, pump limit, rescue cap, feed stop at a time or volume, chemostat steps, aeration steps). Watches: oxygen supply exceeded, respiratory limit (0.95 qS,max), overflow onset, DO below 1 percent and below a setpoint, each held only for excursions of at least 3 minutes Solver: ode.ts with the analytic Jacobian. DEFAULT_RTOL 1e-7 (comment: "the shipped value is being confirmed"), per-state atolFor (fixed values capped by run-derived ceilings), MAX_STEP 0.1 h, DEFAULT_STEP_CAP 40,000 model.test.ts (right-hand side to 1e-12 against the oracle's, a Rosenbrock step against NumPy, RK4 divergence at kLa 1,000/h, tolerances, excursion rule, refusals); fixtures.test.ts (1e-5 relative or a bounded time shift against pipeline/bioreactor-simulator/ oracle runs; quasi-steady cross-check; Dormand–Prince second route; closed-form batch Monod); golden.test.ts (regression records, explicitly not evidence of correctness); science.test.ts
kinetics.ts Monod or Haldane–Andrews substrate term times a Monod oxygen term; Pirt maintenance; Luedeking–Piret product on the substrate budget; CO₂ by carbon balance and O₂ by degree-of-reduction balance; three smooth regimes (growing, product only, maintenance only) with their partial derivatives Non-smooth max(0, ·) switches handled as solver regimes via model.test.ts, fixtures.test.ts
transfer.ts Air saturation by Benson and Krause as the USGS documents it (0 to 40 °C, 0.5 to 1.1 atm); C* from the exhaust-gas balance (a quadratic in y); kLa by Van 't Riet; gassed power by Hughmark as Yawalkar et al. give it; ungassed power with a Rushton Np of 5 Range flags rather than silent extrapolation. Non-coalescing Van 't Riet constants are marked "unverified until Van 't Riet 1979 is read" fixtures.test.ts (USGS DOTABLES to its rounding, Sander's Henry constant, worked gas-balance examples, kLa and range flags)
feed.ts Constant and exponential feed laws as pieces F·e^{μ(t − from)} with exact integrals; exponential feed design rate; pump sheet. FEED_VERSION 1.0.0 Feed stop at Vmax found exactly from the feed law feed.test.ts; feed-oracle.test.ts against the Python decimal reference at 40 digits (verification 0.7.0: 12 feeds, every piece, stop, pump-sheet row and total, and 4 design rates; relative 1e-12)
rescue.ts Oxygen rescue cascade: agitation, then gas flow, then inlet O₂, then (fed-batch) a feed-growth cap; for each lever the smallest change on a grid (10 rpm, 0.05 vvm, 1 point O₂, 0.005/h) that holds DO at the setpoint to the horizon, by bisection over the grid Never exceeds an entered vessel limit rescue.test.ts
scaleup.ts Constant power per volume, constant tip speed, and constant kLa (bisection on the monotone kLa(N) over KLA_RPM_BRACKET, 1 to 10,000 rpm; with no root, noKla names the end that failed, REL-667) between geometrically similar vessels. SCALEUP_VERSION 1.0.0 Out-of-range correlations flagged fixtures.test.ts ("the three scale-up rules"), scaleup.test.ts (the no-result end)
setup.ts, presets/ Change grammar for links (decodeChanges, encodeChanges), cross-field refusals, toRunSpec, klaAt; versioned presets with citations checkSpec refuses or flags, never clamps silently setup.test.ts, presets.test.ts, constants.test.ts; kla-scaling-oracle.test.ts checks klaAt under klasrc value and table against the NumPy float64 reference (verification 0.6.0, 18 settings, relative 1e-12)

src/components/tools/bioreactor-simulator/run-model.ts (computeRun, computeRescue) and sim-job.ts (runJob) turn a run into the view the page draws; sim-job.test.ts runs a job in Node.

Nucleic acid thermodynamics

App page: Sequence.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/seq/tm.ts, tm-bounds.ts Oligo Tm from SantaLucia 1998 unified nearest-neighbour ΔH° and ΔS°: Tm = ΔH°/(ΔS° + R ln(C_T/x)) − 273.15; primer in excess (default, C = primer concentration) or equal strands (C/4; self-complementary x = 1 with symmetry correction); salt by Owczarzy 2004 (default without Mg²⁺), Owczarzy 2008 with its published regimes when Mg²⁺ > 0 (free Mg²⁺ after dNTP binding, Ka 3 × 10⁴ M⁻¹), or SantaLucia's entropy correction Defaults: 250 nM oligo, 50 mM Na⁺. Refused, never clamped, outside the condition bounds (CONDITION_BOUNDS); outsideFit notes outside the fitted ranges (Owczarzy 2004 20 to 1,020 mM; 2008 free Mg²⁺ 0.5 to 600 mM, marked unverified). Only A, C, G, T accepted; offenders named with positions tm.test.ts: within 0.1 °C of Biopython 1.88 on 45 oligos under both models (pipeline/sequence-toolkit/fixtures/oligo-tm.json)
src/lib/seq/structure.ts, structure-params.ts Hairpin, self-dimer and cross-dimer ΔG°37 by a dynamic programme over SantaLucia and Hicks 2004 rules (stacks, bulges, internal loops up to 30, hairpin loops of at least 3, dangling ends, terminal mismatches), salt as entropy with [Na⁺]eq = Na⁺ + 120√(Mg²⁺ − dNTP); ASCII drawings Reports the lowest-ΔG structure (ntthal reports the highest-Tm one). No triloop or tetraloop bonuses. Up to 100 nt (STRUCTURE_MAX_NT). Hairpin terminal-mismatch values come from the Nearest Neighbor Database, licence line "To be checked" structure.test.ts: rescoring ntthal's own structures within STRUCTURE_GATE (at most 0.5 kcal/mol), and the DP minimum equal to exhaustive enumeration on 200 seeded random oligos per engine

Sequence algorithms

App pages: Sequence, Plasmid.

Module Purpose and algorithm Numerical properties Tests and oracle
iupac.ts, parse-core.ts, parse.ts IUPAC alphabets, cleaning, alphabet detection and offender naming; raw, FASTA, multi-FASTA and GenBank input (the GenBank reader is injected so islands do not carry it) Refuse, never guess parse.test.ts
revcomp.ts, gc.ts Complement and reverse complement with ambiguity codes; GC percent with Biopython gc_fraction's default ambiguity rule (S counts GC, W in the denominator, others excluded and reported) GC undefined, not 0, when nothing countable parse.test.ts, translate.test.ts
codes.ts, codes-data.ts, translate.ts NCBI genetic code tables (generated from gc.prt by pipeline/sequence-toolkit/build-codes.mts); translation in six frames, alternative starts optional; ambiguous codons read through all expansions Trailing partial codon dropped translate.test.ts: exact against Biopython 1.88 on 108 frame translations and 5 CDSs
orf.ts ORF finder with NCBI ORFfinder 0.4.3 conventions: most upstream start after the previous in-frame stop, minimum 75 nt, nesting across strands and frames, open-ended ORFs listed apart 1-based coordinates; minus-strand start > end orf.test.ts: exact on 18 cases
motif.ts IUPAC motif search on both strands (overlapping, capped at 5,000 matches); primer binding sites with up to n mismatches and an exact 3′ anchor Capped flag returned motif.test.ts
pcr.ts Exact supplied-pair PCR product prediction, both orientations, linear or one turn of a circle; optional 5′ tails Template 50,000; annealing 8 to 200; no inference of mismatches pcr.test.ts, pcr-review.test.ts
primer-design.ts, primer-design-contract.ts Bounded enumeration of flanking primers (bounded-flanks-v1): windows outside target and exclusions, GC and Tm filters, ranking by 1 × Tm departure + 0.2 × length departure + 1 × pair ΔTm; top 24 per side, 24 pairs checked exactly with predictPcr, five returned; hairpin and dimer checks shown, not used as gates Template 50,000, flank 200, lengths 8 to 40; template SHA-256 recorded (dna-hash.ts) primer-design.test.ts
sequence-correspondence.ts Revision comparison: exact transforms and circular rotations first, then a unit-cost global edit traceback after common flanks (exact-transform-global-edit-v1) Bounded to 1M cells; non-exact circular correspondence withheld sequence-correspondence.test.ts, revision-transforms.test.ts
pack.ts b: 2-bit link codec (header, varint length, packed bases, exception list, base64url) beside the suite's z: deflate form; picks the shorter exact encoding Plain text up to 1,800 letters; round trip checked before use pack.test.ts
fragment-search.ts Literal DNA fragment search on a saved strand, generator-based Query up to 10,000 fragment-search.test.ts
crispr.ts SpCas9 guide sites: every 20-nt protospacer 5′ of NGG on both strands, across the origin once; GC fraction, longest homopolymer, exact repeat count within the pasted sequence No on- or off-target scores crispr.test.ts

Digests, methylation and assembly

App page: Plasmid.

Module Purpose and algorithm Numerical properties Tests and oracle
enzymes.ts Reads the hashed enzyme table (built from pipeline/plasmid-map/enzymes.csv); IUPAC site search on both strands (palindromes once), cut positions in top-strand coordinates, circular search over the sequence extended by the enzyme's reach Biopython's position = after % n + 1 digest.test.ts
methylation.ts dam (GATC) and dcm (CCWGG) marks per host; per-site outcome unmethylated, blocked, not blocked or not recorded from McClelland et al. 1994 Table I; DpnI needs methylation "Not recorded" is never read as a cut methylation.test.ts
digest.ts Enzyme scans under a host, fragments of single or multiple digests, cutter classes, and the diagnostic picker comparing the correct, empty and reversed constructs A digest with one may-be-blocked site gives two pictures; more than one gives none digest.test.ts: against Biopython Bio.Restriction on pUC19 and pBR322 and the oracle's trio verdicts
gel.ts Schematic gel: band order and log-size spacing only; an optional ladder lane drawn as listed Under 100 bp not drawn; a fragment within 5 percent (inclusive) of a band's largest fragment joins that band (gel rules 2) gel.test.ts (hand cases, the ladder lane), digest.test.ts (trio v2)
ladders.ts Ladder band sizes as their suppliers' sheets list them (data, no code beyond a lookup) Never used to size or match a band; licence "To be checked" ladders.test.ts (against pipeline/plasmid-map/ladders.csv)
digest-causes.ts Why a lane has no cut (a list of causes from the digest's census, in a fixed order) and its one drawing Never reads Digest.uncut; off-end counted first digest-causes.test.ts (against digest-causes-v1.json, an independent motif scan)
ds-fragment.ts Typed double-stranded ends: overhang sequence and polarity from (topAfter, bottomAfter); compatibility by antiparallel pairing of exposed bases Never by enzyme name or coordinate ds-fragment.test.ts (EcoRI and KpnI by hand, pipeline/plasmid-map/fixtures/ds-ends.json)
restriction-cloning.ts One vector and one insert digested and ligated into a ring Product length asserted equal to cores plus collapsed overhangs restriction-cloning.test.ts
restriction-cloning.ts, golden-gate.ts, gibson.ts (segments) Where every product base came from: the input, its coordinate and the direction of the walk A cover, not a partition: shared overhangs and overlaps belong to both neighbours, and every product base is covered segment-map.test.ts
feature-transfer.ts A parent's features placed on an assembly product through the segment map, base by base Intact, truncated at a junction or not carried; one row per piece and never a join across bases the parent never had; never stitched across two parents, never silently dropped; no /translation copied and the frame recomputed when the 5′ end is cut feature-transfer.test.ts
cds-translation.ts checkTranslation for an edited or carried coding sequence, and the frame after a 5′ trim A translation only for a whole span with a start and a stop and no internal stop; codonStartAfterTrim is modular, so a trim by a multiple of three leaves the frame alone genbank-export.test.ts, feature-transfer.test.ts
restriction-cloning.ts (assembleRing with one part) Religation: a vector closing on its own two ends compatibleEnds is the test; one enzyme religates, two different enzymes do not religation.test.ts
restriction-from-map.ts Cloning from two sites chosen on the map, one insert, from a PCR product or a second plasmid Withholds for a chosen enzyme that cuts elsewhere, a blocked site, an off-end cut or ends that do not pair; qualifies an uncertain site rather than showing a clean product; leaves an ambiguous orientation to the person restriction-from-map.test.ts
golden-gate.ts (goldenGateMultiPart) Several parts: BsaI and BsmBI only, a cap on the pieces, internal sites with their codons Refuses an enzyme outside the pair, a count over the cap, and a site the circle creates; reports internal sites without changing anything golden-gate-multi.test.ts
golden-gate.ts BsaI and BsmBI assembly decided on overhangs; parts in either orientation Refuses palindromic, duplicate or conflicting overhangs, internal sites, off-end cuts, non-cyclic sets golden-gate.test.ts
gibson.ts 2 to 6 fragments joined on exact terminal overlaps Minimum overlap 15, product up to 50 kb; repeated homology on either strand withheld; length asserted gibson.test.ts
dna-amounts.ts ng ↔ pmol, copies per µL, ng for a copy number, ligation insert mass, from the plasmid's own MW (dna-mw.ts) 650 Da/bp shown only as an approximation dna-amounts.test.ts: exact rational fixtures (pipeline/plasmid-map/fixtures/amounts.json)

Annotation and file formats

App pages: Plasmid, Sequence.

Module Purpose and algorithm Numerical properties Tests and oracle
features/align.ts Banded Smith–Waterman local alignment with Gotoh affine gaps (match +2, mismatch −3, gap −5 − 2(k − 1)) and a three-state traceback With a wide enough band the result equals the unbanded one annotate.test.ts against Biopython PairwiseAligner (pipeline/plasmid-map/fixtures/features.json)
features/annotate.ts Library annotation: 12-base seed words (SEED_K) on both strands for features of 30 bases or more, clustered seeds aligned in a band; exact full-length match for shorter features; circular search over an extended sequence Calls: match at identity ≥ 99 percent and coverage ≥ 95 percent; partial at ≥ 90 and ≥ 50 with ≥ 30 aligned bases. No call does not mean absent annotate.test.ts
features/primers.ts Universal sequencing primers found exactly or with one mismatch (position from the 3′ end), both strands, across the origin Tm only for the perfect match primers.test.ts
features/genbank.ts, genbank-write.ts, extract.ts GenBank flat-file reader (LOCUS, FEATURES, ORIGIN; complement, join, partial ends, joins across the origin) and writer (79-column qualifiers, 60-base lines, the LOCUS date the day of writing or a given date; a hard cut never splits a doubled quote, and a word that fits a line of its own, such as a record reference, starts the line after the opening quote, which the reader joins without a space) Unsupported locations kept as text and flagged genbank-export.test.ts (Plasmid), parse tests
features/snapgene.ts SnapGene .dna reader written from the public packet description: cookie, DNA, features, primers and notes packets Anything unknown refused with a reason; import version above 19 refused snapgene.test.ts
eutils.ts NCBI efetch address and GenBank reading for an accession 50,000-base cap; 400 ms spacing in the client eutils.test.ts

Protein numbers

App pages: Protein, Calc, Sequence.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/seq/protein-mass.ts Average mass from residue masses (ProtParam's table by default, DEFAULT_RESIDUE_TABLE = "protparam" in methods-protein.ts, or CIAAW 2021 formulas); ε280 by Pace et al. 1995 (Trp 5,500, Tyr 1,490, cystine 125) in both cystine conventions B, Z, X, J left out and reported protein-mass.test.ts, extinction-fixtures.test.ts (ProtParam output, fixtures/protparam.json exact to 0.02 Da; fixtures/extinction.json)
src/lib/seq/pi.ts Isoelectric point with the Bjellqvist scale as ProtParam computes it, by bisection on net charge between pH 0 and 14 to 0.0001 Follows ProtParam's C-terminal 3.55, not the published table protein.test.ts, protein-mass.test.ts
src/lib/seq/constructs.ts Affinity tags and protease sites by peptide motif; numbers before and after cleavage The person confirms the site src/components/tools/sequence-toolkit/model-selection.test.ts
src/lib/seq/absorbance.ts A280 concentration by Beer–Lambert; A260 rules of thumb (labelled rough) No nearest-neighbour ε260 via Calc tests
src/lib/seq/dna-mw.ts Nucleic acid MW as an exact CIAAW sum over residue formulas with stated end groups Within 0.01 g/mol on 5 oligos dna-mw.test.ts (pipeline/sequence-toolkit/fixtures/dna-mw.json)
src/lib/seq/peptide.ts Linear peptide SMILES as RDKit's MolFromSequence (flavor 0) writes it Standard 20 residues only peptide.test.ts: identical canonical SMILES under RDKit 2026.03.6

Protein pairwise alignment

App page: Protein. src/lib/protein/align/, a new engine under D-0055 (signed off by the CSO and the CTO on 11 October 2026), deliberately separate from Plasmid's local banded aligner in src/lib/seq/features/align.ts: src/lib/protein/boundary.test.ts forbids imports in either direction and allows the aligner only ./ imports.

Module Purpose and algorithm Numerical properties Tests and oracle
blosum62.ts BLOSUM62 in half bits over ARNDCQEGHILKMFPSTWYVBZX*, generated by pipeline/protein-explorer/blosum62.py from NCBI's file (stored with sha256 85510d38…efb40) and checked entry by entry against Biopython 1.88's copy (576 entries, symmetric) Do not edit align.test.ts re-reads the stored file, checks its hash, every entry and symmetry
policy.ts Modes global (terminal gaps free in both sequences, the default), global-end-gaps (terminal gaps charged), local; BLOSUM62, gap cost 10 + 0.5(k − 1). Doubled scores, GAP_OPEN2 20 and GAP_EXTEND2 1. prepare first refuses a length product above CELL_LIMIT 10,000,000, then empty text, U/O, internal stop/pre-gapped input and letters outside the matrix; uppercases text and removes one terminal stop, reporting that removal. ENGINE_VERSION is protein-align 1.0.0 Exact doubled integers; no DNA scoring, banding or configurable cell limit align.test.ts: typed refusals, terminal-stop metadata and all three modes
gotoh.ts Gotoh's affine-gap programme: E(i,j) = max(H(i,j−1) − 20, E(i,j−1) − 1), F likewise vertically, H = max(diagonal, E, F), floored at 0 in local mode; rolling Int32Array rows and one tie byte per cell recording every optimal source; global free ends take the best of the last row and column (row 0's last cell included, since the CSO's C2 fixture), penalised the corner. traceback follows a fixed tie order (diagonal, E, F; extend before open) About 10 MB at the cell limit; no Int32 drift align.test.ts against Biopython 1.88 PairwiseAligner (align_reference.py): score exact, shown alignment re-scored to the optimum, its counts among the enumerated tuples
correspondence.ts uniquePartners: a backward pass from every optimal end over the tie bits with two Uint8 rows of marks, giving each residue of A its partner in every co-optimal alignment (0 if none); coOptimalCount: the number of optimal alignments over the same bits with rolling Float64 rows, capped (the panel uses 1,000). Global modes only Linear in the cells; no per-cell table beyond the trace align.test.ts: count equal to Biopython's; unique pairs equal to the intersection of Biopython's enumerated alignments, as sets
report.ts alignPair (the worker's one entry point): identities, similar pairs (BLOSUM62 above 0), gap columns and opens over the columns, identities over each length, gapped strings, differences in each entry's numbering, partners, co-optimal count X–X is identical but not similar (DEF-W4-4) align.test.ts (45 tests); EMBOSS Needle 6.6.0.0 cross-check of four runs (docs/science/evidence/protein-align-2026-10-11/)

Measured cost: about 370 ms per mode in Node for 3,162 × 3,162 random residues including the co-optimal pass (builder), 197 to 333 ms in Node by the CTO, and a browser median of 622.9 ms at 9,999,440 cells on the merge candidate (the co-optimal count adds about 250 ms at the limit).

Stage F's src/lib/protein/residue-map.ts sits outside align/ and imports only its prepare, forward and uniquePartners; D1 imports nothing back. It fixes global/free-end mode, returns reciprocal invariant pairs, classifies only standard-residue identity as exact and standard mismatches as differs, and withholds all other transfer positions. It keeps actual raw and ASCII-uppercase sequences with optional unvalidated provenance. Missing sequence is unavailable; every stop and non-ASCII input is refused. Each length is capped at 10,000, the product at 10 million, padded trace at 10,020,001 bytes and a conservative typed-array sum at 12 MiB before alignment. The sum includes encodings, trace, four forward Int32 rows, two correspondence mark rows and both partner arrays together; JS strings/arrays/objects and runtime overhead are excluded. It is not a process-memory measurement. No report strings or alignment count are built. The readiness-gated client loader applies one 10-second deadline to import, hashing and worker work, settles cancellation during a held import and forwards only the remaining deadline. Evidence imports the shared binding/input/refusal leaf directly; full wire-response validation stays in the optional client closure. Its marker is drybench-island:protein-residue-map-client; all three new Protein closures have measured birth budgets in Build, test and CI.

residue-map.test.ts matches all 47 v2 Biopython outcomes exactly and allows only the dedicated mapping worker as a runtime importer. Source confidence uses the strict Float64 confidence.ts parser and exact-only projection, independently referenced by the frozen Protein 0.6.0 fixture. The worker recomputes actual sequence/confidence SHA-256 bindings and returns validated reciprocal arrays. Its owner aborts hashing/execution and terminates on success, cancellation, failure, malformed response or the 10-second operational watchdog. The scientific performance target remains a quiet-host 2-second median. Separate legacy placement remains tagged compatibility data for AlphaMissense: above 10 million cells only cheap equality/unique-containment branches may run, and above either 10,000-length bound nothing is dispatched. Integration buffer accounting excludes clone/JS-object/runtime overhead; the foundation allocation remains its unchanged conservative typed-array sum. See Protein.

Pairwise DNA alignment

App page: Sequence. src/lib/seq/align/, B-0's engine (the CSO's Sequence-stage memo of 12 October 2026, item 1; the CTO's memo, (a)(i)); built on claude/sequence-dna-aligner and not merged until the CSO and the CTO sign off with its decision record. It is separate from Plasmid's annotation aligner (src/lib/seq/features/align.ts) and from the protein aligner: boundary.test.ts allows it only ./ imports, forbids either of them importing it, and pins features/align.ts's SHA-256 so it stays byte-identical. A worker is drafted (src/components/tools/sequence-toolkit/dna-align.worker.ts, one job per worker); readDnaAlignRequest rebuilds the input and the options from their named fields alone, with listUpTo and cap integers from 1 to 1,000 (listUpTo never above cap) and cellLimit from 1 to 25,000,000 (the CTO's F2), so a test switch such as banded never crosses it. No view or size budget yet.

Module Purpose and algorithm Numerical properties Tests and oracle
policy.ts Modes global (every end gap penalised), semiglobal-read (end gaps free on the read only), local; match +2, mismatch −3, any ambiguity code 0, a gap of k bases 5 + 2(k − 1); prepare refuses, in order, an empty input, a read over 20,000 or a reference over 1,000,000 letters, a letter outside A C G T and the eleven codes (U only in a record declared RNA, read as T), a chosen region outside the reference (or backwards on a linear one), a region covering a whole circle, global on a circular reference without a region, a read longer than a circular reference (with or without a region: the CSO's final reading, ruling 2 (3)); a region (from, to, across a circle's origin allowed) is searched as a linear stretch, global allowed on one shorter than the circle; IUPAC reverse complement Integers throughout align.test.ts (the fixture's twelve refusals, exact)
method.ts DNA_ALIGN_METHOD: a three-state affine-gap programme, as Biopython 1.88's PairwiseAligner (listed there as 'Gotoh (three-state)'); attribution to Gotoh 1982 not checked against the paper. Its citation is the CSO's route (c) text under D-0108 (her ruling of 14 October 2026 on the aligner's sources, ruling 1): the recurrences in full, with Biopython 1.88's pairwise chapter as the reference and no DOI; the Gotoh record stays in the file's header comment, not yet read; no imports. The limits line is product copy, in content/apps/sequence-toolkit/methods/align.ts (the CTO's B.11) content/apps/sequence-toolkit/methods/align.test.ts
band.ts A band is a range of diagonals i − j (reference letters minus read letters consumed); layout stores the cells with i, j ≥ 1 inside it row by row; row 0 and column 0 are worked out, never stored; cellsIn via align.test.ts (the fixture's cellsNeeded)
gotoh.ts align: Biopython's three states (M a pair, X a reference letter against a gap in the read, Y a read letter against a gap in the reference), so adjacent gaps of opposite kinds and the co-optimal count are Biopython's; semiglobal-read's free gaps in columns 0 and m worked out, its ends where the read's last letter is placed; local starts after a pair that floors at 0, ends on a pair at the optimum, never passes through one, and its gaps never fall to 0. Two bytes per stored cell: nine tie bits, three "on a counted path" bits, a local-end bit. countPaths counts paths forward (rolling Float64 rows, saturating one past the cap so exactly the cap is told from more), applying Biopython's local rule and the circular drop. traceback: the published tie order. mark: one backward marking pass giving the diagonals, the marked graph (per stored cell, the states on an optimal path and those with a pair still ahead) and, exactly and without counts, the nodes on every optimal path: a node is common when it is the only marked node at its level i + j, no marked pair move jumps that level, and every path's origin lies below it and its end at or above it (the proof is below). groups: one pass forward over the marked graph (the CTO's F3) carrying, per node, the smallest first paired text index of the paths reaching it and a label born at a path's first pair; labels join at a pair, at a gap node with a pair ahead (two prefixes through it share that later pair), and on a circle at the same pair of the reference in the text's other copy, so the groups linked by shared pairs, each group's spans (the union of its alignments' first-to-last paired spans), read interval and the longest paired span of any one path come out exact at any count. foldedCommon: on a circle, where a node and its copy n letters on are both marked, a node of the reference is common when the paths through its copies, counted exactly in BigInt over the marked graph, add up to all of them (the CSO's final reading, ruling 4 item 2); where no copy pair is marked, the level rule decides alone. align refuses a table over the request's limit (at most 25,000,000 cells); place.ts reports that refusal as band-limited for cells. enumerate: the co-optimal alignments in the published tie order when there are at most the listing cap, each held as a Listing (semiglobal-read's free end gaps as two lengths, lead and trail, every other column in columns), stopping at the column bound; compactListing and expandListing convert to and from whole column lists. (the builder's seeded exit and entry bound is retired by the CTO's B.1) 20 MB of trace at the 10,000,000-cell limit (the CTO's estimate was one byte). Measured in Node, 12 October 2026, 3,161 × 3,161 random bases, both strands: 1.7 to 3.0 s for the three modes; not yet measured in a worker. Until the CTO's count-free rule lands (REL-446), foldedCommon on a circle holds a BigInt and a Map entry per marked node: on A×5,000 circular with CCC + A×997 (5,017,988 marked nodes) the fold took 26.8 s of a 35.9 to 44.6 s two-strand call at about 1.7 GB peak (the CTO's ruling of 20 October 2026 on the D pass, 2.3); and the passes after the table are outside the band-limited catch (REL-447) align.test.ts: every value the fixture records, under a test-only column bound (state, score, count and overCap at the defaults, Biopython's block, uniquePairs, the selected alignment with every number, overhang and difference, the band block recomputed, the groups with their sizes and bands, hull, the placement flags, variation, the numeric tuples, the listed alignments in the published tie order, and a listing cut short by the bound equal to the oracle's prefix); common-nodes.test.ts: the level rule against BigInt path counts (below); difference-common.test.ts: common and the shared pairs against enumeration; cell-limit.test.ts
circular.ts The CSO's certified circular text (architecture ruling, section D; the CTO's addendum, item 2): the reference followed by its first m − 1 + D letters; free starts kept below row n (the drop rule, keepBelow); breaksFor: b = ⌊(2m − S)/2⌋; positions modulo n; crossesOrigin; seed diagonals folded onto one turn alignDna runs with D = m (or a requested D0), certifies D ≥ b, else reruns once with D = b; with the rerun switched off (a test hook) the strand is "not determined"; the read-check form (one run at D = m) certifies "no acceptable placement" where nothing scores above 0. Holds a wrap past one turn circular-text.test.ts: every circular fixture case under D = m and under the full certificate from D0 = 0 (reruns and text length asserted), the hook giving "not determined", the forced cases counted, 200 seeded circular pairs equal to a doubled text kept for tests only, and a full-length read of a rotated 100-base circle (rotations 0, 1, 50 and 99, semiglobal-read and local: score 200, one alignment, identity 100/100, no differences, read base 1 at r + 1, crossing the origin exactly when r > 0) the same rotations on the minus strand (base 1 as supplied at reference position r), a period-4 circle placing a rotated read 25 ways, no pair common and no wrap (the CSO's reading of 6cf9cbf5, ruling 4.4), and her counter-example to an unqualified "ends whole" (the CSO's ruling 1(b) of 20 October 2026: a read of ref 1 then ref 3 to ref n has two equally scoring alignments in semiglobal-read, one group over the whole circle, one alignment leaving ref 1 unreached; in local the read's first letter is left out instead, 196 against 193); align-long-read.test.ts for 7j
place.ts A table within the cell limit L is searched whole. Above it, with banded acceptance off (BANDED_ACCEPTANCE = false, the default), the strand is band-limited (the CTO's B.10), as is a table within the request's limit that the engine will not allocate: the reason with the cells and the limit, the shared 12-base word count (the CTO's unit: one per read position and reference position whose 12 letters agree, A C G T only, an ambiguity code resetting the word; on a circle each reference position once, starts 0 to n − 1; above SEED_HIT_CAP it reads 100,000 with sharedWordsOverCap), the reference ranges where those words lie, the narrow-region hint L / m and the way forward; no alignment and no number. With it on (per call, for tests, until the CSO has read the head): the CTO's B.1 rule, the CSO's Q3(c) bound. Seeds are every shared 12-base word over the whole text searched, a circle's two copies included, on unfolded diagonals; one band per seed cluster (each its own table, since no path crosses between disjoint bands; their union at most L); a first pass 16 diagonals beyond the seeds, then at most one rerun at b0 + 1; accepted when m > 12b + 11 (semiglobal-read) or S > 22b + 22 (local) with b = ⌊(2m − S)/2⌋ (differencesUpTo, the CSO's word); otherwise the full table if it fits L, else band-limited (no seed guarantee); no optimal path touching a band's edge is asserted; SEED_HIT_CAP (100,000) and global are band-limited. The route taken (whole, banded, banded-rerun, fallback) is on the result See SCI-213 property.test.ts (the CTO's B.5): 170 pairs from fixed seeds over ten generators (17 each), every first table asserted inside 20,001 to 100,000 cells (the CSO's final reading found 9 pairs under it: sized gave references shorter than the read; it now takes a minimum length and allows for mutated reads' lengths), banding forced above 20,000 with the fallback within L, so no strand is searched whole; zero tolerance on outcome, score, capped count, columns and shared pairs; every route at least ten times (banded 46, rerun 11, fallback 283). The CTO's rule (his ruling of 14 October 2026 on the listing bound, as amended by his ruling 5 of 20 October 2026): the 10 s ceiling is read in the fast chain's unit step as the median of three ms lines for the candidate head (its run and two reruns of its checks job, or runs on heads with an identical src/lib/seq/align/ tree), with cpuMs (process.cpuUsage()) logged beside it for information; above 10 s the count falls one pair per generator at a time (at 16, N_SPACED 11 and 35), to a floor of 140, under which the question goes to the CTO; the listing lever is rejected, since it drops strands from the lemma's checks; the tolerance never moves; the in-test guard stays at 20 s, applied in CI only (process.env.CI), the logged line saying whether it applied (bound); every other assertion runs on every host and the test is never skipped. On 20 October every reading on the 200-pair test was over 10 s (11,341, 11,533, 10,950 and 13,765 ms), and identical code read 8.00 against 11.34 s, so one reading is not a measure; the count went to 17 a generator in one step. Readings at 170 pairs, on 2aea997c (Fast chain run 39499120831, attempts 1 to 3, 20 October 2026): 6,008, 6,051 and 6,015 ms (CPU 8,593, 9,005 and 8,870 ms); median 6,015 ms, under the 10 s ceiling. A fourth reading, not part of the median: 10,278 ms (CPU 14,753 ms) on 71823eb6, the same src/ tree (Fast chain run 39503684384; the CTO, 21 October 2026). The n-spaced test's own lines read 366, 330 and 340 ms, holding nothing. Any later change to src/lib/seq/align/ takes three fresh readings: after the decision's renumbering (D-0108, comments in method.ts), on 37cb7c00 (Fast chain run 39515988212, attempts 1 to 3, 21 October 2026): 10,404, 7,992 and 6,309 ms (CPU 15,077, 11,326 and 9,129 ms); median 7,992 ms. The count filter's lemma (the CTO's ruling 5 of 20 October 2026; the CSO's sources ruling, 4 item 4): for each aligned strand of the whole-table run and up to 50 listed co-optimal alignments a strand, with S its own score and b = ⌊(2m − S)/2⌋, the 12-base A C G T words on its own columns (counted in the test) number at least m − 12b − 11, its columns span at most b + 1 diagonals (unfolded on a circle) and each word is a seed; minimum slack logged (0 in both modes). The first test checks it on its own pairs (41 strands with a positive bound in semiglobal-read, 16 in local). A second test runs the n-spaced pairs (N every 12 or more letters, 11 or more from either end: S = 2(m − k), b = k), the whole-table call only, which meet the bound exactly: 10 in semiglobal-read and 34 in local (never fewer than 10 a mode; the CTO's rule is to add them where the sample misses the floor, never to loosen an assertion); with the first test's counts every mode has at least 50 strands with a positive bound (51 and 50), and it fails if those counts are absent. Its own ms line is recorded and holds nothing. The count filter itself is not built; it will sit behind an off constant in policy.ts until this test is green in the Fast chain and the CSO has read it. align.test.ts: band-widened and no-unbroken-word band-limited under a tight L and the full table (247) by fallback under a roomy one
report.ts alignDna, the one entry point: both strands, the strand (or ambiguous, none, band-limited, not-determined) with strandUndetermined (equal optima, each strand's placement unique and both hulls the same interval: ACGA against ACGT), the region when one was chosen, and per strand one of aligned, no-alignment, wraps-past-one-turn, no-acceptable-placement (read-check form), not-determined, band-limited (its reason per strand). no-paired-letter (semiglobal-read where any co-optimal alignment pairs no letter, which is exactly when the optimum is one gap of the whole read, −(5 + 2(m − 1)): the score and the full count, nothing placed; the CSO's re-reading, D4). The strand is none where the better strand, or either of two equal strands, places nothing (D5). Aligned carries the score, the count (exact up to the cap, counted to cap + 1, overCap above it), uniquePairs (reference positions, in read order: the shown alignment's pairs that are common) and uniquePlacement, placementNotDetermined (one group, no common pair; never in global), noCommonColumn (global with no common pair), the hull (hullOf: on the certified circle the shortest arc holding every alignment's span, wholeCircle where they cover it, of equally short arcs the one not crossing the origin, else the one starting lowest; on a linear text, a region of a circle included, least to greatest in the text's own order, D3), the tie-order alignment with Biopython's coordinates (on a circle without the free trailing gap), columns, identity (withheld at a zero denominator; ambiguity columns apart), readAligned and referenceCovered as spans (coverage over the region when one was chosen), overhang with at (reference-end past a linear reference's own end, region-edge past a chosen region's), placement and differences (each with common: a substitution or ambiguity column by its own node (the CSO's re-reading, D1); an insertion or deletion only when its run and both bounding nodes lie on every optimal path, a free end gap bounding it adding no condition (D2); notFixed anchors on one that is not: the nearest common pairs either side, refAfter after the last letter consumed with a free leading gap counted, lettersCanAgree on ambiguity, crossesOrigin on a gap across a circle's origin, and positions as supplied on the minus strand), circularText on a circle, and the placement groups (exact at any count, from groups, each with its hull and read interval, as supplied on the minus strand). Positions are in the reference's numbering, a region's offset applied. The wrap past one turn is decided from the groups' longest span, so a wrap among unlisted alignments is found. Listing (for the oracle and the tests; Align shows no list, the CPO's words) runs up to listUpTo alignments and stops before the alignment that would take the listed total past LIST_COLUMN_LIMIT (2,000,000, an Estimate), counted in the CTO's unit (ruling of 14 October 2026): an alignment's columns less its free end gaps, which is exactly columns in global and local and columns plus the overhang on both sides in semiglobal-read. Enumeration holds the free end gaps as two lengths, not columns, and applies the bound as it runs, so a listing holds at most 2,000,000 columns plus a constant per alignment, whatever the reference's length (listedColumns reports the held total, a test hook). Above the bound listedAll is false, and variation and the group sizes are null, never estimated; the score, count, shared pairs, groups, hull and wrap come from the marked graph and are unaffected. The worker never passes listColumnLimit, the tests' bound. A job processes at most 3L cells No E-value, no significance, no relatedness align.test.ts, align-long-read.test.ts (7j), listing-bound.test.ts (a 768-alignment tie-dense read stops at the column bound), listing-memory.test.ts (the CTO's 450 kb case, 100 placements of a 20-base read: all listed at the defaults, 2,000 held columns, 3.5 s and 248 MB peak here against 18.5 s and 3,954 MB before the fix; the CSO's 89-base case lists whole), circular-text.test.ts; mutation checks below
positions.ts Not read by the CSO; not used: present on the merge head and reached by no surface, export or caller (the CSO's ruling 6 of 20 October 2026 on the merge head carrying it). The read check's per-position summary (the CTO's ruling 4 of 20 October 2026; the CSO's read-check ruling 2): built from mark's marked graph, never from listed alignments, only when AlignOptions.positions is set (read-check requests; Align never sets it) and the strand's placement is one group with a common pair. Two scans of the table count the marked cells and fill one Int32Array of their stored indices (ascending, so a source is found by binary search, and a cell's row and column come back from the layout), with no Map entry or JS array per node or cell (the CTO's red line, his D-pass ruling 2.4); a forward and a backward pass over them, with each node's marked sources (markedSourceMask), find the nodes between the first and last common pairs on some path. Over that counted stretch every optimal path consumes each reference position once: a position is paired with read j when every node there consuming it is M at (·, j) (copies n apart on the certified circle are one position, through foldedCommon), deleted when every one is X, else variable. Read positions a..b are a common insertion after r when every node consuming each is Y at r and those consuming a − 1 and b + 1 are M at r and r + 1 alone: the same two flanking pairs in every alignment (ruling 2.6, which corrects the CTO's rule naming r alone). Reference positions consumed outside free end gaps before the first or after the last common pair are the varying end (ruling 2.5); global reaches the text's ends Working set on read-check requests (the CTO's ruling 6 of 20 October 2026): 8 bytes a marked cell (the index 4, flags 2, sources 2), and marked cells are at most all cells, so up to 80 MB at the default 10,000,000-cell limit and 200 MB at the 25,000,000 maximum on a tie-dense table, over the table's own working set; insertions is a JS array of one entry per common insertion, at most m/2 (10,000 at READ_CAP); the read-check worker's gate runs the tie-dense cases with the summary on. Columnar: kind (Uint8Array, 0 paired, 1 deleted, 2 variable) and read (Int32Array, the 0-based read position, −1 when not paired), moved by the worker's transfer list (transfersOf), not cloned; insertions and varyingEnd 1-based. No count, no cap, no listing; its cost on 7j is within run-to-run noise (4.2 s with it, 4.5 s without, this host, 20 October 2026) positions-off.test.ts: with the summary off, every result equals 6cf9cbf5's (the head the CSO read) on the fixture's 60 cases in each mode and 300 seeded pairs, by a sha256 over every result taken from 6cf9cbf5's own engine (the CSO's condition 2). positions.test.ts: equality, zero tolerance, with states computed afresh from every enumerated alignment by ruling 2: (a) 360 seeded tie-dense pairs (homopolymer and tandem-repeat indels, single-base indels in separate runs, local starts, circles across the origin and twin copies), every mode, both strands, 1,874 strands compared; (b) every dna-align-v1 strand Biopython lists whole, from its own alignments; (c) one strand of 1,296 alignments (four runs apart by common pairs), enumerated whole. Mutation checks: dropping either flanking-pair condition, or the fold, fails it

The level rule for common nodes

The graph G holds the nodes (a state at a cell) on counted optimal paths and the counted moves between them; every path runs from an origin ((0, 0) in global and semiglobal-read, a local start in local) to an end (the corner in global and semiglobal-read, the free trailing run down column m counted as part of the path; a local end in local). The level of a node at (i, j) is L = i + j. Every move raises the level by 1 (a gap: X from (i − 1, j), Y from (i, j − 1)) or by 2 (a pair: M from (i − 1, j − 1)), so a path visits each level at most once. Claim: a non-origin node v at level L lies on every optimal path if and only if (a) v is the only non-origin node of G at level L, (b) no move of G goes from level L − 1 to level L + 1, and (c) every origin of G lies below L and every end at or above it.

  • Sufficiency. Take any optimal path P, from origin o to end e. By (c), level(o) < L ≤ level(e). Levels along P rise by 1 or 2, so P either has a node at level L or steps from L − 1 to L + 1; that step is a pair move of G, which (b) excludes. So P has a node at level L; it is not an origin (P's only origin is o, below L), it is in G, so by (a) it is v.
  • Necessity of (a). If G holds another non-origin node w at level L, some optimal path passes w; it visits level L once, at w, so it misses v.
  • Necessity of (b). If G holds a pair move from L − 1 to L + 1, some optimal path takes it; that path never visits level L, so it misses v.
  • Necessity of (c). If some optimal path's origin is at level L or above, the path's other nodes are above L and its origin is not v (v is not an origin), so it misses v. If some optimal path's end is below L, all its nodes are below L, so it misses v.

mark counts the non-origin marked nodes per level (rows 0 and column 0 and semiglobal-read's trailing run worked out, not stored), records each marked pair move's jumped level, and takes the greatest origin level and the least end level. Outside local every path starts at (0, 0), so the CTO's generator "semiglobal-read origins after the node's level" cannot occur; the origin condition decides only local nodes here. common-nodes.test.ts (the CTO's conditions on accepting the method) holds it to path counts: 500 seeded pairs over seven generators (tiny pairs, mutated, homopolymer runs, tandem repeats, ambiguity codes, and two circular ones on the certified text with the drop rule), grids of at most 5,000 cells, both strands, every mode the topology allows (2,677 tables, 68,325 nodes); a second programme written for the test (a port of the oracle's) counts paths forward and backward in BigInt over G, and for every node the engine's isCommon equals fwd × bwd = total, the rule stated from G agrees, and the engine's stored marks equal G's interior nodes exactly (equality, not inclusion). Each condition alone decides some node (the jump condition 5,095 times, the origin condition 250, the end condition 432), so removing any one fails the test. 1.8 s here on 12 October 2026, inside the CTO's 3 s.

Mutation checks run by the builder on 12 October 2026, each caught by align.test.ts: the three state priorities of the tie order, Biopython's local no-extension rule, the circular drop, the lowest-coordinate end, the band bound (without it band-widened returned 145 where the optimum is 247); after the fixture ruling, the level rule's jump and origin/end bounds, semiglobal-read's trailing free nodes, start overhang, the supplied base's complement, readAligned as a span, banded acceptance on by default, and the seed guarantee with the bound and margin removed together (each of the guarantee and the bound alone is masked by the other on these cases); after the architecture rulings, the certificate (accepting every run without its check fails nine circular cases), the Q3(c) guarantee (accepting every banded result fails the property test and align.test.ts) and the rerun (without it the rerun route is never reached).

Units, formula weights, dilution and preparation

App pages: Calc, Assay.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/units/quantity.ts, convert.ts, quantity-entry.ts One base unit per dimension and every other unit an exact power of ten (scaleByPow10 moves the decimal point of the shortest decimal form); unit text normalisation (NFKC, µ variants, "l"); n = C·V, m = n·M. OD600 is its own attenuance dimension No accumulated float error across conversion chains; temperature excluded units.test.ts, quantity-entry.test.ts
src/lib/units/unit-converter.ts, temperature.ts The lab unit converter: exact decimal-string scaling within one quantity (BigInt digits, no floating point); molar bridges only through a supplied formula weight (floating point, 12 significant figures); a named refusal for anything that needs a density, basis or calibration. Celsius and kelvin by the exact offset 273.15 on the decimal text, refused below 0 K Prefix changes and temperatures exact as strings unit-converter-oracle.test.ts (Python fractions, 44 cases)
src/lib/units/atomic-weights.ts, formula-mass.ts CIAAW 2021 abridged weights (with the 2024 Gd, Lu, Zr revisions) as integers in 10⁻⁴ g/mol; formula parser with groups, hydrates and charges; exact integer sums rounded once at display, half away from zero Imatinib mesylate shows 589.72 (exactly 589.715) formula-mass-fixtures.test.ts: exact CIAAW sums by Python decimal (pipeline/lab-calculators/reference.py)
src/lib/units/mass-concentration.ts, molar-from-mass.ts Mass concentration to molar through a stated molar mass (A-4 item 10): a compound's M from its formula for the stated form (no default for a multi-part formula; no bare MW), a protein chain's M from its sequence (ProtParam or CIAAW 2021 residues, a residue range), c = c(g/L)/M; [E]active = [E]total × n × f for kcat Full precision; masses shown to two places from the exact decimal molar-from-mass.test.ts against pipeline/assay-analyser/oracles/units_molecule.py (fixtures 0.11.0, recorded in the oracle image)
src/lib/dilution/c1v1.ts, stock.ts, recipe.ts C1V1 = C2V2 for any unknown; mass for a stock with purity, "weigh what you weighed", parent equivalents; multi-component recipes Base units; nothing rounds dilution.test.ts, fixtures.test.ts: Python fractions.Fraction
src/lib/dilution/serial.ts, dmso.ts Serial dilution plan (2.0.0, constant transfer): every tube, the last included, R·W·f/(f − 1), transfer R·W/(f − 1); solvent carried from the stock; pipetting flag strictly below 2 µL; DMSO warning at 0.5 percent Up to 24 points; flags compared on exact values fixtures.test.ts, serial-2-oracle.test.ts (Python fractions, 1e-9)
src/lib/dilution/allowances.ts batchWithAllowances(N, V, p, D) = N·V·(1 + p/100) + D: the one allowance rule the master mix, the antibody batch and the plate sheet call via those engines' oracle tests
src/lib/dilution/plate-sheet.ts A serial plan made into a plate preparation sheet: tubes sized from the allowance rule with constant transfer, optional intermediate, vehicle made from neat carrier, dose positions from proposePlacement, dispense rows by well and totals Any refusal gives no sheet; dead volume once per source. Method plate-sheet 1.0.0 plate-sheet-oracle.test.ts (Python fractions, 22 cases, 1e-9)
src/lib/dilution/buffer-recipe.ts Mass of the exact form m = C·V·M (CIAAW sum) and the pH-meter setpoint pH_target + pK(T_prep) − pK(T_work) by Goldberg equation 12 Only the 15 twice-transcribed buffers; 274 to 350 K buffer-recipe-oracle.test.ts (NumPy 2.5.3 and Python decimal; setpoint within 0.01)
src/lib/dilution/intermediate.ts One-intermediate dilution feasibility: reduces the constraints to an interval in the intermediate ratio r (with a quadratic root bound), then picks the smallest feasible final transfer and stock draw No pipette resolution inferred; nothing rounded to rescue a plan intermediate.test.ts, intermediate-oracle.test.ts, preparation-oracle.test.ts (intermediate-dilution.json, an exact grid, 13 cases)
src/lib/dilution/master-mix.ts Master-mix preparation arithmetic Up to 40 components master-mix.test.ts, src/lib/dilution/preparation-oracle.test.ts (master-mix.json, Python fractions, 14 cases)
src/lib/dilution/buffer.ts Buffer pK at temperature by Goldberg, Kishore and Lennen 2002 equation 12 (Clarke and Glew form) with the linear dpK/dT shown as an approximation R = 8.31451, Tr = 298.15 K; method 1.1.0 gives no pK outside 274 to 350 K fixtures.test.ts (NumPy, values transcribed a second time)
src/lib/preparation/calculate.ts, contract.ts, kinds/ Everyday preparation on canonical quantities, keeping the original units, dispatched to one module per kind (cells, OD600, centrifuge, percent, antibody), each with its own method record and receipt Results re-read by readPreparationResult (balances 1e-10) preparation.test.ts, preparation-oracle.test.ts, od600-oracle.test.ts, antibody-batch-oracle.test.ts (Python fractions, 1e-9)
src/lib/preparation/od600-calibration.ts An OD600 estimate from the person's own pairs of OD and counted density, through fitCalibration (linear, free intercept), read back only inside the densities measured Method od600-calibration 1.0.0 od600-calibration.test.ts (unit-tested; the calibration engine itself is checked against calibration-linear-exact.json)

Plate and table parsing

App page: Assay.

Module Purpose and algorithm Numerical properties Tests and oracle
src/lib/table/delimited.ts Byte decoding with BOM handling (UTF-8, UTF-16 LE and BE, unmarked UTF-16 by zero bytes), delimiter detection (tab, comma, semicolon), RFC 4180 fields, numbers with either decimal mark Decimal comma only when the delimiter is not a comma table.test.ts
src/lib/table/plate.ts 96- and 384-well plate blocks in three profiles (grid, softmax-pro, gen5); header metadata discarded Profiles written from synthetic files in pipeline/assay-analyser/plates/ table.test.ts
src/lib/table/plate-map.ts Well roles, assignments, filled series, curves from a layout, placement proposals Concentrations converted to mol/L when curves are made plate-placement.test.ts
src/lib/table/well-ranges.ts, sheet-placement.ts Typed well ranges (A1:A3, H12) to wells, naming a bad token; a Calc plate sheet placed on an Assay layout through proposePlacement, blanks written, vehicle and positive wells never written Whole placement blocked by any collision; stale proposals refused sheet-placement.test.ts
src/lib/table/tables.ts Long and wide response tables with units in headers Mass concentrations refused without a molar mass table.test.ts
src/lib/table/import-review.ts Import review records with per-cell issues Up to 4 MiB table.test.ts

Cheminformatics on RDKit

App page: Molecule. All in src/components/tools/molecule-workbench/; run in the RDKit worker and in the pipeline.

Module Purpose and algorithm Numerical properties Tests and oracle
chem.ts descriptorsOf: RDKit's get_descriptors (amw, exact mass, Crippen cLogP, TPSA, NumHBD/NumHBA and Lipinski counts, rotatable bonds, rings, Fsp3, heavy atoms, stereocentres), formula from the molecule JSON; prepareInput describes the largest organic fragment of a salt unless a metal complex is involved; Morgan fingerprints under policy morgan-r2-2048-described-v1 (radius 2, 2,048 bits, useChirality: false, useBondTypes: true, includeRedundantEnvironments: false, all passed explicitly) on the described fragment Size limits before parsing; Coordgen used only within COORDGEN_LIMITS chem.test.ts against Python RDKit 2026.03.6 (fixtures/rdkit-reference.json); full-library comparison in the pipeline; policy.test.ts against fingerprint_policy.py
versions.ts, library-checks.ts The fingerprint policy, descriptor and RDKit version constants, written once; libraryCompatibility refuses a library computed under other versions policy.test.ts (including a drift test of RDKIT_VERSION against the vendored version)
series.ts, engine.ts describeSeriesRow A person's series: table reading, the confirmed mapping, values with qualifiers, pIC50 for IC50 in molar units (qualifier inverted), same-compound rows, within-series Tanimoto, the saved record with its versions At most 200 rows series.test.ts against series_reference.py (floats 1e-6, QED and pIC50 1e-9, counts exact)
qed.ts QED (Bickerton et al. 2012), a port of RDKit's QED.py; aromatic-ring count computed as the cycle rank after removing aliphatic-ring atoms Largest difference from Python RDKit 2.8e-16 over 2,197 molecules (pipeline README, 10 October 2026) chem.test.ts
pains-data.ts, engine.ts PAINS substructure matches with highlighted atoms in the depiction 124 library drugs match at least one filter (pipeline README) chem.test.ts
rules.ts Lipinski rule of 5 (Crippen logP, labelled so) and Veber's rules logic.test.ts
similarity.ts, engine.ts Tanimoto on packed bit vectors with popcount; every same-compound library row (whole or described canonical SMILES, the shipped sameCompound pairs, then an InChIKey check of the top 50) removed from the ranking and returned as forms; ten returned Linear scan over 2,197 drugs logic.test.ts; policy.test.ts (forms and top ten equal Python's; Tanimoto within 1e-12)
map-model.ts Chemical-space map: PCA of nine standardised descriptors fitted once in the pipeline by cyclic Jacobi eigen-decomposition; new molecules projected with stored means, SDs and loadings; ten nearest in the plane Component signs fixed so the largest loading is positive; loadings agree with NumPy to 4.22e-15 (pipeline README) logic.test.ts
domain.ts, limits.ts Which structures scores apply to; organic elements and counter-ion metals; heavy-atom counting from SMILES text logic.test.ts

The map is drawn on a 2D canvas in src/apps/molecule-workbench/panels/map-panel.tsx with viewport helpers in map-view.ts.

Classifiers and joins in Pipeline

App page: Pipeline. All in src/components/tools/drug-pipeline-explorer/, pure modules shared by the page and pipeline/drug-pipeline-explorer/.

Module Purpose and algorithm Tests
modality.ts Rule-based modality classifier for interventions: placebo-like names skipped, keyword rules, antibody-drug conjugate stems, a short named list, then WHO INN stems (Stem book 2024, checked by pipeline/drug-pipeline-explorer/verify_stems.py) on each word; anything else "unclassified" modality.test.ts
why-stopped.ts Keyword grouping of stop reasons, with denials ("not due to safety") removed first and consequences tried last why-stopped.test.ts
aggregate.ts Status groups, periods, quantiles and top-12 sponsors behind the charts and the saved snapshots; since Wave 4 registry coverage (coverageOf), start year by phase and the sponsor figure's counts with individual-class sponsors grouped aggregate.test.ts; coverage.test.ts (exact against coverage_oracle.py on a frozen search)
ctgov-parse.ts API v2 studies into Trial records, now keeping the raw LeadSponsorClass ctgov.test.ts, coverage.test.ts
sponsor-aliases.ts Short anchored rules merging one group's registered sponsor strings; owned companies kept apart sponsor-aliases.test.ts
precedent/nme.ts, crl.ts, confirm.ts, pivotal.ts, review-time.ts, designations.ts FDA precedent table: joins by exact application number (CDER compilation), name and date (Project Confirm, 98 percent precision threshold), complete response letters before original approval, review time from receipt to approval, designation flags with programme start dates nme.test.ts, crl.test.ts, confirm.test.ts, pivotal.test.ts, examples.test.ts, snapshot-age.test.ts

Protein Explorer selection logic

App page: Protein.

  • src/components/tools/protein-explorer/structures.ts: one option per PDB entry scored as resolved residues × a resolution factor (1 up to 2 Å, falling linearly to 0.5 at 4 Å, 0.4 beyond or for unresolved non-NMR methods, 0.5 for NMR); an entry whose chain is at most 95 percent identical to the UniProt sequence (MIN_IDENTITY) counts half once identities are known. Tests: structures.test.ts.
  • src/components/tools/protein-explorer/alphafold.ts placeModelSequence: places an AlphaFold model on the current UniProt sequence by exact containment either way, or by the best ungapped diagonal at least 95 percent identical over an overlap of at least 90 percent of the shorter sequence, and refuses when gappedAlignmentCompetes (sequence-placement.ts, global affine alignment +2/−3/−5/−1) finds an equally or better supported gapped path. Tests: alphafold.test.ts.
  • alphafold.ts parseMissenseCsv: checks every AlphaMissense row's reference letter against the model's own sequence and withholds the whole entry on any mismatch (DEF-W4-2, zero tolerance); keeps the rows with their decimal count and the per-residue mean and n. maskModelDifferences removes means where the placed model differs from UniProt (DEF-W4-3). Tests: alphamissense.test.ts (n exact, mean 1e-6, against alphamissense_reference.py).
  • substitution.ts: reads one substitution in five notations and returns AlphaMissense's row only when the model is placed with zero differences, the file passed the check, and the row's reference equals both the model and the UniProt residue. Tests: substitution.test.ts against substitution_reference.py.
  • variants.ts drops variants reported only by COSMIC (its licence does not allow commercial reuse, per the header).

Teaching models

App page: Education. src/apps/education/experiments/ holds small deterministic models, each with a test file: a fixed-volume growth and oxygen model with explicit fixed steps (applied/growth-model.ts, 2,000 steps per hour, illustrative parameters), dose-response samples on src/lib/fit/curve.ts with a seeded Box–Muller generator (quantitative/models.ts), one-site competitive binding fractions and an orthographic Cα projection (structural/models.ts), and sequence and nucleic-acid exercises on src/lib/seq/translate.ts and revcomp.ts. They reuse the suite kernels rather than re-implementing them.

Rendering helpers

  • MolViewSpec builders: src/components/tools/protein-explorer/mvs.ts and src/components/tools/inside-a-cell/mvs.ts (Cell scenes are always the multi-state form with one snapshot so no camera animation is added).
  • Depiction palettes: src/components/tools/molecule-workbench/depiction-palette.ts gives RDKit drawing options per theme, with atom colours gate-tested at 4.5:1 on the dark panel. RDKit's SVG output is inserted with dangerouslySetInnerHTML (for example in structure-thumb.tsx).
  • Landing reel: src/components/showreel/ (about 10,000 lines) is a 2D-canvas particle and typography engine for the landing page's intro (core/renderer.ts, core/swarm.ts, player.tsx). It never plays under reduced motion, Save-Data, slow connections or for crawlers; an inline failsafe turns the overlay off after 3 s if the bundle has not taken over and in any case after 18 s; device-pixel ratio steps down from 2 to 1.5 to 1 within a 9,000,000-pixel budget and when frames run slow. It is marketing code with no scientific content and is budgeted under landing-js in scripts/size-budgets/suite.json.

Search catalogues and indexes

Catalogue files

The suite's offline name resolution uses six content-hashed files in public/data/search/, built by scripts/build-search-catalogs.mjs and scripts/build-search-details.mjs through scripts/lib/search-catalog-files.mjs, which also writes src/lib/search/catalog-urls.ts. Sources and licences are in public/data/search/sources.json; see Data pipeline and Data sources.

Key File Rows Raw bytes Gzip 9 bytes Row shape
compounds compounds.e83eedfbcc1e.json 16,784 1,922,739 574,315 [chemblId, name, aliases[], maxPhase, moleculeType]
conditions conditions.6b8bf99bee3b.json 12,335 1,195,878 273,375 [doid, label, exactSynonyms[]]
proteins human-proteins.7a02fce00848.json 20,431 3,136,357 1,002,223 [accession, entryName, names, gene, synonyms, length]
proteinSeeds protein-seeds.9b1503e3e889.json 120 24,182 8,288 as proteins
compoundDetails compound-details.566c9c008576.json map 79,128 23,982 { id: [text, number] }
conditionParents condition-details.0c7c42a820b7.json map 633,531 87,416 { id: parentText }

Loader

src/lib/search/catalog.ts is the one loader (CTO memo of 10 October 2026, docs/board/memos/2026-10-10-cto-search-catalogues-out-of-the-bundle.md). loadCatalogs() loads once from the registered source, at most one load at a time, and clears its memo on failure so the next call retries. requireCatalogs() throws CatalogUnavailable until the three identity catalogues (compounds, conditions, proteins) are in; identity producers call it only on name branches, so a ref built from an identifier (an NCT id, a UniProt accession, a ChEMBL id) never waits. The two detail maps and the protein seeds never block identity.

src/lib/search/catalog-client.ts registers the browser source: plain fetch with credentials: "same-origin" and a 60 s abort (REL-439: with "omit", a preview behind Vercel's login refused every catalogue, so Molecule could not prepare a compound's identity and the J3 walk's step 1 never got its "send to Calc" action), a shape check per file before anything is put in (a truncated file is "unavailable", never half a catalogue), protein seeds put in as soon as they arrive, and detail maps that fail left empty. The files are fetched with plain fetch rather than the suite network layer so that a missing same-origin file does not mark a public service as cooling down. src/lib/search/use-catalogs.ts (useCatalogs(active)) starts loading only on intent: opening the palette, focusing a catalogue-backed field or opening an object page. src/lib/search/catalog-server.ts (server only) imports the compounds and protein seeds statically for share cards and the /api/search/compound and /api/search/molecule lookups.

Index

createNameIndex in src/lib/search/index.ts serves compounds, conditions and proteins:

  • searchKey folds for search: NFKD, combining marks removed, lower case, apostrophes dropped, other non-letters and non-digits to spaces.
  • search(text, limit = 10) ranks each term by its best name: 0 exact, 1 the label starts with the query, 2 another name starts with it, 3 the label contains every query word, 4 another name does; ties by shorter label, then catalogue order. It is a linear scan over precomputed folded names.
  • resolve(text) uses an exact-key map built on first use. An alias shared by two records maps to null (ambiguous, never resolved); a record's own label or id overrides an alias. The compound index uses a stricter identity key (compoundKey: NFKC, lower case, apostrophe variants unified, whitespace collapsed) that keeps punctuation, because punctuation can encode stereochemistry, isotopes or charge.
  • src/lib/search/compounds.ts adds UNII aliases from curated-structures.ts, which currently holds one verified structure (retatrutide, from NIH GSRS).
  • src/lib/search/proteins.ts merges seeds before the full list and deduplicates by accession; subjects.ts mixes the top three of each kind for literature subjects.

Tests: src/lib/search/catalog.test.ts (loading, retry, listeners, detail maps never blocking identity) and catalogs.test.ts (identifiers before load, ambiguous aliases, chemical forms not collapsed by folding, seed ordering).

Fuzzy search in Literature

App page: Literature. fuse.js is used in two client modules:

  • src/lib/literature/search/concepts.ts builds subject rows from the same catalogues and a Fuse spelling index on first use (threshold 0.2, ignoreLocation, minimum match 4). Only names of 6 to 160 letters and spaces enter the spelling index; gene names and all-capital names are excluded, so identifiers are never spelling-corrected.
  • src/lib/literature/search/saved-papers.ts filters already-scoped saved papers: DOI, PMID, PMCID and OpenAlex ids match exactly; other queries match as substrings, and Fuse (threshold 0.22; title 0.6, authors 0.25, journal 0.15) adds fuzzy matches only when every query term is at least five letters.

Remote literature results are merged on the server by src/lib/literature/search/rank.ts: reciprocal rank fusion with k = 60 (RANKING_VERSION "rrf-60-v1") and deduplication on DOI, PMID, PMCID and OpenAlex identifiers that pass format checks (DEDUPLICATION_VERSION "validated-identifiers-v1").

Other small catalogues have their own linear searches: the 75-row reagent table in src/components/tools/lab-calculators/reagents.ts (searchReagents, every query word contained) and UniProt suggestion ranking in src/components/tools/protein-explorer/uniprot.ts (rankSuggestions).

Gaps, divergences and risks

Collected for Known issues. Each item was found while writing this page.

  1. Stale comment on 1/Y² weighting. src/lib/fit/four-pl.ts:10-13 says the weighted residual (y − f)/f is "minimised directly with its full Jacobian"; the code at four-pl.ts:335 uses relative IRLS (frozen 1/f² weights) unless historicalRelativeLoss is set, as methods.ts (WEIGHTING_METHOD) and irls.test.ts describe.
  2. Duplicated fit version. src/lib/fit/calibration.ts:6 declares its own FIT_VERSION = "2.0.0" instead of importing it from src/lib/fit/index.ts; the two can drift.
  3. Design system out of date on worker CSP. docs/design/suite-design-system.md:1695 (§11.5) says /_next/static/:path* gets the workspace CSP and a Playwright check asserts it on the worker script; content/csp.test.ts:240-265 and e2e/smoke/apps.spec.ts:70 assert the opposite (foundation decision C11). Worker scripts therefore run without a CSP of their own.
  4. Stale references in the 3D shape code. src/components/tools/molecule-workbench/three-dmol.ts:2 mentions a "/tools page" and src/apps/molecule-workbench/panels/shape-panel.tsx:11 a viewer-3d.tsx; neither exists in the repository.
  5. Copy in a component. src/apps/molecule-workbench/panels/shape-panel.tsx:111, :191 and :201 hard-code user-facing error text, against the rule that copy lives in content files.
  6. Untested engine version literals (partly addressed in Wave 4). RDKIT_VERSION now lives in src/components/tools/molecule-workbench/versions.ts with a drift test against vendorVersions (policy.test.ts), and the worker refuses a library whose rdkit differs from the running module's; the OpenChemLib version in exports is the worker's runtime version, and bundledVersions.openchemlib is tested against package.json, the installed package, the runtime and the Molecule credit line (src/lib/vendor.test.ts). Still untested: other content/tools/*.ts and registry source names, and the Mol* cspAllowances reason strings.
  7. OpenChemLib tables not version-checked. No test compares _version in public/data/molecule-workbench/ocl-resources.json with the installed openchemlib.
  8. No size budget for the engines. RDKit.js (about 2.4 MB gzip), Mol* (about 1.4 MB gzip), 3Dmol.js (about 0.16 MB gzip) and the OpenChemLib chunks have no automated budget; growth on upgrade would go unnoticed.
  9. Workers with no timeout. The qPCR, calibration, inhibition, primer-design, sequence-review and protein-alignment workers (qpcr-panel.tsx:49, calibration-panel.tsx:67, primer-design-panel.tsx:82, sequence-revision-panel.tsx:39, align-client.ts) can only be cancelled by the person or a new request; a hung computation shows as busy indefinitely.
  10. No browser test for the no-WebGL fallback. None of the three fallbacks is exercised by e2e/.
  11. Software rendering handled for Cell only. Protein's Mol* viewer does not apply SOFTWARE_RENDER_PROPS and does not subscribe to WebGL context loss.
  12. Engine lifecycle workarounds. Mol* retains heap after dispose() (molstar-view.tsx:150-153) and 3Dmol.js 2.5.5 cannot dispose a viewer (three-dmol.ts:22); both are worked around with long-lived viewers and must be re-checked on upgrade.
  13. Mol internal API.* Both wrappers call Mol* plugin internals (canvas3d, managers.structure, helpers.viewportScreenshot, PluginConfig.Viewport) that are not a stable public API.
  14. Catalogue parsing on the main thread. src/lib/search/catalog-client.ts parses about 7.0 MB of JSON (about 2.0 MB gzip) on the main thread once catalogues are requested. Its effect on responsiveness has not been measured.
  15. Two atomic-weight tables. Molecule reports RDKit's average molecular weight (chem.ts, amw), while Calc and the sequence kernels use CIAAW 2021 conventional weights; src/lib/units/formula-mass-fixtures.test.ts:1-10 records that they differ (S 32.067 against 32.06, Cl 35.453 against 35.45). The same compound can show different weights in two apps.
  16. Ported third-party code. qed.ts and pains-data.ts carry RDKit-derived code and data with the BSD notice reproduced. Whether this meets the licence is held for the General Counsel.
  17. Scientific inputs marked unverified in code. Van 't Riet non-coalescing constants and range (src/lib/hosts/transfer.ts:20), the Owczarzy 2008 free-Mg²⁺ range (src/lib/seq/tm.ts:44), the Sebaugh bend constant's paper (src/lib/fit/rules.ts:18), the Nearest Neighbor Database licence line (src/lib/seq/structure-params.ts:27) and the shipped bioreactor rtol (src/lib/hosts/model.ts:460).
  18. Worker gates outside required CI. Under D-0047 Bioreactor and protein-align run weekly and on demand on separate scheduled.yml matrix runners (CTO, 10 and 11 October 2026). Two-core hosts may still be inconclusive; a release needs a quiet-host pass within seven days (docs/NEXT.md, REL-06 and REL-12). Targets and guards are unchanged.
  19. Aligner gaps recorded by the reviews. Co-optimal tie coverage is thin (largest fixtured count 8); the oracle checks U and O before the cell count while prepare checks the cells first, and no fixture exercises both; sameKey in align-protocol.ts compares a request's key with itself; X–X counts as identical but not similar (DEF-W4-4). The BLOSUM62 table's redistribution terms are held for the General Counsel.

Published Molecule similarity check (23 October 2026)

The offline Python RDKit 2026.03.6 oracle (BSD-3-Clause) is now registered for the ten-query molecule.morgan-tanimoto set in verification version 0.3.0. The publisher reruns the unchanged policy implementation from library SMILES in memory and checks byte-identical existing output; no browser engine or shipped dependency changes. The public manifest pins actual source bytes and records honest publication/reproduction dates. The publication test checks RDKit.js query bits and described/excluded/ranked identities exactly, and unrounded scores within 1e-12; the separate content bridge checks six-decimal display rounding. This checks implementation against the same RDKit release, not independent experiments. See Oracles and verification.