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

Sequence

The empty workspace opens on local sequence paste with labelled examples and a separate UniProt protein lookup route. It has no empty subject header or operation tabs. Once a sequence is loaded, the compact lookup, editor and operation tabs appear. The lookup's X clears only the lookup; New sequence clears the subject through the pending-work guard. The lookup remains mounted during a transition from an empty editor to a loaded view, so a late UniProt response stays reviewable rather than disappearing.

D-0060 (11 October 2026): the method inspector opts into docking once the selected workbench has explicit inputs, with blank starts transiently hidden before hydration. Only a blank/work transition changes that transient state; explicit choices made on this visit are preserved across edits and blank/work transitions. Phones remain hidden until opened.

Sequence (registry slug sequence-toolkit, version 0.8.0, status preview) computes everyday answers from one pasted DNA, RNA or protein sequence: reverse complement, translation, ORFs, GC, nucleic acid mass, motifs, protein mass, pI and ε280, oligo Tm, primer checks, exact PCR products, bounded primer design and a design revision review. This page describes how each part is implemented, the parameters and references behind every number, how the numbers are checked, and what is known to be incomplete.

Purpose, users and scope

The design brief (docs/design/brief-sequence-toolkit.md, intent) positions Sequence as the suite's "bench utility": a page per operation that answers above the fold, plus a workbench that holds one paste and offers every operation on it. Its named audiences are undergraduates and instructors (versioned teaching examples), graduate students and postdocs (primer checks, construct ε280), core facilities and small-biotech scientists. The brief also makes Sequence the owner of the shared sequence engine, src/lib/seq/, which Plasmid, Calc and Protein also import.

What the code does today, in scope order:

  • Every scientific calculation runs in the browser. The only network call the app makes on its own behalf is an explicit UniProt protein search and record fetch (see External services).
  • Since 17 October 2026 the thirteenth operation is mutagenesis-primers, the site-directed mutagenesis primer design rebuilt under D-0096 (rebuild 3) and D-0090 from the CSO's contract of that day, fixture first; it is both an operation page and a workbench operation in the Primers group.
  • Since 10 October 2026 the workbench also contains a bounded primer-design search, reusable primer sets and a sequence revision review (the "app depth" increment, docs/design/pre-release-app-depth/molecular.md M1 to M3). Since 11 October 2026 (Wave 6 item 1) primer design is one of the twelve public operations, with its page, methods entry, limit and published fixture, and the FAQ says so; the revision review remains a disclosure in the workbench.
  • Restriction digests, Gibson and Golden Gate assembly, CRISPR guides, methylation and feature annotation live in src/lib/seq/ but belong to Plasmid; see Plasmid. qPCR analysis belongs to Assay (e2e/qpcr.spec.ts is an Assay test); see Assay.
  • Not implemented: codon usage, selectable pK sets, antibody numbering, Sanger trace review, NCBI accession fetching (Plasmid has it; Sequence does not), buffer presets.

The app's research-use statement is in content/tools/sequence-toolkit.ts (limits[0]) and the registry sets notMedicalAdvice: true.

Addresses and entry points

Route segments come from the route map (content/apps/routes.ts, segment sequence); see Registry and content.

Address What serves it Notes
/sequence The product page, from content/apps/sequence-toolkit.ts (page) Indexable
/sequence/app The workspace route with src/apps/sequence-toolkit/entry.tsx Never indexed; state after #s=
/sequence/<operation> src/apps/sequence-toolkit/tasks.marketing.tsx, one server component per task id, with a client island from task-islands.tsx Thirteen operation pages: reverse-complement, translate, six-frame, orf-finder, protein-mw-pi, extinction-coefficient, oligo-tm, primer-check, gc-content, dna-molecular-weight, motif-search, primer-design (its worked example is the fixture case design-lac-linear; the page is an async server component because the design hashes the template), mutagenesis-primers (its worked example is the hand-worked fixture case hand-substitution-overlap)
/sequence/insulin, /sequence/gfp, /sequence/lacz-alpha The same file Versioned teaching examples insulin@1, gfp@1, lacz-alpha@1
/docs/sequence Docs page, from content/apps/sequence-toolkit.docs.ts, with the verification table in src/apps/sequence-toolkit/verification-table.marketing.tsx #methods is the chips' "methods" link target
/verification/sequence-toolkit/<version>/ Static files under public/verification/sequence-toolkit/ Published fixture copies; see Verification
/share/sequence/<subject> Shared-link preview Only for an authored operation id; see Storage and state

src/apps/sequence-toolkit/page-ids.ts declares no extra static pages under /sequence/app/.

File map

App glue (src/apps/sequence-toolkit/)

File Role
definition.ts AppDefinition: Main, rail (examples only), keys 1 to 9 and p, palette commands, method inspector (conditional docking for explicit inputs, blank starts hidden; D-0060), share schema, usesUrlState: true
entry.tsx Client entry; displayName drybench-entry:sequence-toolkit is the size-budget marker
workspace.tsx SequenceMain: loads the workbench with next/dynamic inside ToolBoundary, keyed on the workspace scopeEpoch
views/workbench.tsx The workbench (827 lines on 12 October 2026): paste, address, arrivals, saved work, operation groups, exports, hand-offs, primer design and revision panels
views/sequence-search.tsx "Find a protein sequence": local suggestions and explicit UniProt search
store.ts publishBench/readBench/useBench: the workbench's state for shell-rendered parts (commands, inspector)
inspector.tsx Method inspector: equation, constants tables, verification sentences, SEQ_VERSION
objects.ts Sequence and construct ObjectInputs, inbound rules, payload hook, sequenceForLink
object-tab.tsx, object-tabs.ts The "Sequence" tab on sequence, target and construct object pages
provenance.ts Constants-table to registry-source map, chip provenance, table labels
rows.ts METHODS_BY_OP and the CSV rows per operation
exports.ts Export menu items and the provenance block
workspace-inputs.ts Saved-work payload (sequence-workspace v2), reader, annotated GenBank, construct text for Plasmid hand-offs
workspace-adapter.ts Recording one operation's result as an immutable result snapshot
pcr-product.ts, pcr-product-writers.ts Predicted PCR product record and its strict reader (pcr-product.ts, which Plasmid's admission worker loads, comparing with the validating canonical JSON from src/lib/workspace/canonical-json.ts); its FASTA/GenBank/JSON writers are in pcr-product-writers.ts, so the worker carries none of them (REL-23) (the GenBank writer makes LOCUS, DEFINITION, /label, /note and COMMENT plain ASCII with asciiNote, src/lib/seq/features/genbank-ascii.ts, the rule Plasmid's writer applies; SCI-09; qualifiers wrap only at spaces, a long word on one line past column 79, so the record reference and receipts read back exactly; such lines may fail strict submission validators, and these files are never submissions; REL-379)
primer-sets.ts, primer-set-csv.ts Primer-set collection entries and strict reader; the order CSV in primer-set-csv.ts (REL-23)
protein-import.ts UniProt record to FASTA text with source fields; source round-trip through the address
sequence-import-guard.ts Revision snapshot that blocks a late lookup from replacing newer input
import/ Stage B import layer (not yet wired to a surface): detectors.ts (one Detector per kind and isGzip), sequences.ts (GenBank, FASTA and plain-letter readers of a confirmed kind), readers.ts (each kind's reader, loaded on demand), outcome.ts (the set outcome), types.ts; see Import detectors
tasks.marketing.tsx, task-islands.tsx, islands/*.tsx Operation and example pages and their per-operation client islands
verification-table.marketing.tsx, exact-answers.ts Docs verification table; exact-match engine answers in fixture shape (build and test only)
*.test.ts Unit tests (see Verification)

Domain code and panels (src/components/tools/sequence-toolkit/)

File Role
address.ts sequenceSchema: every #s= key with its check; OPERATION_KEYS, PASTE_KEYS
model-core.ts, model.ts loadPasteWith/loadPaste (the Loaded record), applies, fingerprint, display helpers; model.ts is the barrel the workbench and tests use
model-<operation>.ts One pure adapter per operation: revcomp, translate (also six frames), orf, protein, tm, primer, structure, gc, dna-mass, motif, pcr, selection
tm-limits.ts The Tm limit line and fitted-range notes, shared by card, CSV and recorded result
scan-source.ts Exact identity of a scan's inputs for exports and recorded results
examples.ts The three versioned examples
protocol.ts, seq.worker.ts, worker-client.ts, use-scan.ts Scan worker: limits, message types, client, hook
primer-design.worker.ts, sequence-review.worker.ts Module workers for primer design and revision review
panels/*.tsx One panel per operation, plus sequence-box, selection-summary, structure-cards, pcr-product-panel, primer-design-panel (with primer-design-workbench and primer-design-walk, the workbench's wrapper and walk binding, and primer-design-collection, loaded on demand in the workbench), mutagenesis-primers-panel (with mutagenesis-results, its candidates table, loaded once a design has run), sequence-revision-panel, parts

Shared engine (src/lib/seq/, the parts Sequence uses)

File Content
iupac.ts Alphabets, cleanSequence, detectAlphabet, offenders, codeMatches
parse-core.ts, parse.ts, features/genbank.ts Raw, FASTA, multi-FASTA and GenBank reading
revcomp.ts Complement and reverse complement with IUPAC codes
codes-data.ts (generated), codes.ts, translate.ts, orf.ts NCBI genetic codes, translation, frames, ORFs
gc.ts, motif.ts, dna-mw.ts GC percent, motifs and binding sites, nucleic acid mass
protein-mass.ts, pi.ts, constructs.ts, peptide.ts Protein mass and ε280, pI, tags and protease sites, peptide SMILES
tm.ts, tm-bounds.ts Nearest-neighbour Tm and salt corrections
structure-params.ts, structure.ts Hairpin and dimer ΔG°37
pcr.ts, pcr-limits.ts, primer-design.ts, primer-design-contract.ts, sequence-correspondence.ts, fragment-search.ts, dna-hash.ts Exact PCR products (with the engine's input limits in an engine-free module), primer design, revision review, exact fragment search, SHA-256 of residues
sdm-primers.ts, methods-sdm.ts Site-directed mutagenesis primers in two schemes, and their method record and published rules
pack.ts The seq link codec (b:, z:, plain)
methods*.ts, version.ts Method records (MethodRef) and SEQ_VERSION (1.1.0)

Content

content/apps/sequence-toolkit.ts (registry entry and product copy), content/apps/sequence-toolkit.entry.ts, content/tools/sequence-toolkit.ts (write-up, sources, limits), content/apps/sequence-toolkit.methods.ts with content/apps/sequence-toolkit/methods/*.ts (method records joined with verification state), content/apps/sequence-toolkit.verification*.ts (fixture counts, dates, badge sentences), content/apps/sequence-toolkit.workspace.ts with content/apps/sequence-toolkit/*.ts (workspace copy, split per operation so an island carries only its own words).

Pipeline and browser tests

pipeline/sequence-toolkit/ holds the oracle scripts, saved inputs and fixtures (see Verification). e2e/sequence-toolkit/ holds seven Playwright specs; e2e/perf/two-second.spec.ts holds Sequence's two-second test.

Workbench structure and state

Layout

Workbench renders, top to bottom: the UniProt search field (SequenceSearch), the subject header (h1 in the workspace, h2 in a preview) with save controls, three operation groups as line tabs, and one tab panel. Inside the panel the paste box (SequenceBox) and the selected operation's panel sit side by side from a 48 rem main-container width and stack below it. One <details> section follows for saved DNA work: "Review sequence revisions". Primer design, once a disclosure here, is the primer-design operation's own panel since 11 October 2026 (primer-design-panel.tsx, with the primer-set collection loaded on demand from primer-design-collection.tsx); when the paste is not one A/C/G/T DNA record within 50,000 bases the panel says what it needs. Panels other than the selected one are not rendered.

The groups (content/apps/sequence-toolkit/workbench.ts, railCopy.groups):

Group Operations
Transform reverse complement, translate, six frames
Inspect ORF finder, protein MW and pI, ε280, GC percent, DNA molecular weight, motif search
Primers oligo Tm, primer check, primer design

Each group remembers its last operation for the visit (lastOperation ref). The operation id in the address decides the group; no group key is written. Keys 1 to 9 open the first nine operations in OPERATION_IDS order (reverse complement through GC percent), p focuses the paste box; DNA molecular weight and motif search have no key (definition.ts, KEYS).

State in the address

All link state is in the #s= fragment (see Storage and state). op is the subject key: changing it pushes a history entry; every other key replaces. The schema is sequenceSchema in address.ts.

Key Meaning Check
op Operation id One of the 12 ids (OPERATION_IDS); default reverse-complement
mine Saved-work or construct ref to open Hand-off dictionary
seq, alphabet, name, id, range, ex Paste (plain, b: or z:), stated alphabet (protein, dna, rna, dna-circular), name, UniProt accession, residue range, example id Hand-off dictionary (src/lib/handoff/params.ts); plain seq at most 1,800 letters
seqsource UniProt source record as JSON (readProteinSequenceSource) At most 3,000 characters, strict field checks
frame 1,2,3,-1,-2,-3 One of
code Genetic code id 1 or 2 digits, at least 1; an id NCBI lacks falls back to table 1
ts, im Stop at first stop; first codon as Met 1 only
min, alt ORF minimum coding length (nt); alternative starts Integer 3 to 100,000; 1
motif, both Motif; both strands Letters only, at most 60; 0 or 1
mm Mismatches allowed in binding diagnostics Integer 0 to 3
o2, ftail, rtail Reverse primer; forward and reverse 5′ tails Upper-cased, no alphabet check (deliberate: the panel explains invalid text); at most TAIL_LINK_GUARD (400) characters, a link guard and not a sequence limit, pinned by a test to stay at or above the engine's limit (the CTO's ruling of 12 October 2026): a tail over the PCR engine's PCR_LIMITS.tail (200) arrives intact and the engine refuses it by name (importing the limit into the address regroups every island's chunks by about 1 KB, measured 12 October 2026)
tpl, ptop PCR template; template topology Hand-off seq check; linear or circular
oc, na, mg, dntp Oligo nM; Na⁺, Mg²⁺, dNTP mM Non-negative number within 0.001 to 1,000,000 nM; 0 to 2,000; 0 to 1,000; 0 to 100 mM (refused, never clamped; tm-limits.test.ts holds these equal to CONDITION_BOUNDS)
cm, sc Equal-strands model; SantaLucia 1998 entropy correction equal; santalucia
str, top, p5 Double strand; circular; 5′-phosphate double; circular; phosphate
cut Cleavage confirmed 1

A typed paste is written to the address 250 ms after the last keystroke, as sequenceForLink encodes it, only if the encoded value is at most 1,800 characters (SEQ_LINK_MAX in objects.ts). Otherwise seq is cleared, inLink becomes false and the paste box says it stays in this browser. With a UniProt source the whole address must also fit LINK_MAX_LENGTH. The template is written to tpl only when it is plain A/C/G/T of at most 1,800 bases. Results are never written to the address.

The seq codec (src/lib/seq/pack.ts): for nucleotides, the shorter of plain letters and a b: payload (2 bits a base, A 0, C 1, G 2, T/U 3; a header with an RNA flag and a varint length; an exception list of varint position deltas and character codes for any other letter; base64url), plain winning ties and b: forced above 1,800 letters; z: (deflate, from url-state.ts) if the b: round trip is not exact. Proteins are plain up to 1,800 letters, else z:. There is no topology flag in b:; circularity travels as alphabet=dna-circular. In practice about 5,400 bases of DNA fit in 1,800 characters of b:.

Arrivals, examples and private records

On load the workbench resolves the paste from, in order: a seq it did not write itself (decoded, given a FASTA header with name and alphabet, or rebuilt from seqsource); a versioned example (ex without seq), whose operation and settings seed the view until the first edit copies them into the address; or a target's residues found in the local object store by the alias uniprot:<id>. A mine ref opens a private construct record: if its data.app is sequence-toolkit the full saved inputs are restored; otherwise constructSequenceText builds FASTA or, for a Plasmid construct, GenBank text with its features. The arrival strip reports each source once.

Bench store and announcements

publishBench publishes the operation, alphabet, length, keepable, inLink, name and actions on every render, for the definition's commands and the inspector. 700 ms after the paste, operation or state changes, the text of the visible answer in #primary is announced through the host's live region; a changed answer withdraws the previous announcement.

Saved work and recorded results

The "Save to Bench" controls (WorkCaptureControls) appear when the paste is one record of at most 1,000,000 residues. The subject's cluster is Save to Bench, Record result, pin and ⋯; "Open annotated DNA in Plasmid" is an item of ⋯ (disabled, with the 50,000-base limit in its words, over that length), and "Use a Bench input" sits beside the paste box (UI-16). The lookup's privacy sentence is body text (UI-26). The payload is SequenceWorkspaceData (schema sequence-workspace, version 2):

{ v: 2, app: "sequence-toolkit", residues, alphabet, name,
  sequenceWorkspace: { v: 1, text, templateText, state, sourceContext },
  workspace?: WorkspaceAppMetadata, pcrProduct?: PcrProductRecord, primerSets: PrimerSet[] }

readSequenceWorkspace re-checks every state key through sequenceSchema, re-parses text and requires its residues and alphabet to equal the stored ones, re-verifies any PCR product by recomputation, and accepts primer sets only in version 2. Size limits on read: text 2,000,000 characters, template 1,000,000, source context 20,000, name 2,000. A PCR product is kept only while the saved residues still equal the product's sequence; after an edit the source context records "Edited from a predicted PCR product".

Kept edits. Edits to a named task are kept as its draft beside the record, never written into it by the timer or by leaving (D-0068 rule 8; Storage and state). When the mine open path opens Sequence's own saved inputs, it looks up the live task that owns the record (readOpenWork) and that task's draft (readOpenWorkDraft); it shows the draft when readSequenceWorkspace accepts it, else the record, through showInputs, which sets the inputs, the address settings and appliedDraft in one update (a refused draft is offered as unshown). The owning task is put in the research context, so a bookmark of #s=mine= alone opens the task with its offer. reloadOpenInputs serves onDraftResolved: Discard puts the saved inputs back, its Undo the draft, each checking the open ref and the scope before it sets anything. Saved inputs that name no source take the record itself as a fresh source-sequence binding, and a version 1 record gains an empty primer-set list, so the payload rebuilt from the screen can differ from the stored data with no edit: a saved record put on screen is held in savedShown until its text, template and settings show, then savedOnScreen is bumped so the control takes them as the task's baseline. A shown draft keeps its own bindings. Sequence has no autosave of its own; its object actions act on the content-addressed sequence object (sequenceRef), not the task's record. e2e/objects/work-draft-sequence.spec.ts covers the offer, Discard and Undo, a resume with no edit and the address alone.

"Record result" freezes only the selected operation (captureSequenceWorkspaceResult, schema sequence-toolkit.recorded-result v1). It stores the input snapshot, settings, the CSV rows and the raw results, method ids and versions, data versions (constants tables), and qualifications with codes such as sequence-calculation, sequence-alphabet, sequence-operation-refused, tm-outside-fitted-range, motif-match-cap, primer-prediction, binding-unavailable. The reporting status is refused, qualified or reportable. A versioned example is recorded as a published teaching dataset. Primer design and revision review results are not recorded this way; they have their own JSON exports. The shared mechanics are in Storage and state.

Scan worker and size limits

Constant Value Where
INLINE_LIMIT 100,000 letters: at or above, six-frame, ORF and motif scans go to the worker protocol.ts
MAX_INPUT 1,000,000 letters: over it nothing is computed (overCap); text over 1,100,000 characters is not parsed protocol.ts, model-core.ts

useScan computes inline below the limit and posts to one shared SeqWorkerClient above it. The client runs one worker (seq.worker.ts), with a 60 s timeout per request; Cancel terminates the worker and the next scan starts a new one. The worker re-checks the 1 Mb cap and imports only the pure engine; it never fetches. Translation, reverse complement, GC, masses and protein numbers always run on the main thread, at any accepted length. The worker's binding request type is defined but no caller sends it; binding diagnostics run inline and are skipped for templates at or over 100,000 bases.

Input parsing

Formats

parseInputWith (src/lib/seq/parse-core.ts) reads:

Format Detection Result
GenBank flat file A line starting LOCUS after trimming One record per entry via readGenBank: locus name, definition, accession aliases from VERSION/ACCESSION, topology, molecule type, features. A LOCUS length that differs from the ORIGIN length adds a warning
FASTA, multi-FASTA Text starting > One record per > chunk; header key=value tokens become fields (alphabet, circular, accession, name, and UniProt source fields); accession aliases from the header
Raw Anything else One record, no name

The workbench computes on the first record only; with several records it says so, and saving is disabled. Task islands load the GenBank reader on demand and read a GenBank paste as nothing until it has loaded. GenBank location syntax and features are described in Plasmid.

Accession aliases (accessionAliases): UniProt from sp|…|/tr|…| or a bare first token matching the UniProt pattern, RefSeq (XX_ plus 6 to 9 digits, optional version), otherwise GenBank/EMBL (1 or 2 letters, 5 or 6 digits, optional version).

GenBank molecule type sets the alphabet: …RNA… with a U in the sequence reads as RNA, AA or protein as protein, anything else as DNA (an mRNA record written with T reads as DNA).

Cleaning, alphabet detection and offending characters

cleanSequence removes every character that is not a letter, * or -, and upper-cases the rest. Digits and punctuation of numbered listings are dropped silently; letters are never dropped.

detectAlphabet on the cleaned letters (with * and - removed):

  • any letter outside the 16 nucleotide codes (A C G T U R Y S W K M B D H V N) gives protein, "sure";
  • otherwise, if at least 90 percent are A, C, G, T, U or N: RNA when there is U and no T, else DNA; "sure" only with at least 10 letters and 100 percent plain letters, else "likely";
  • otherwise protein, "likely".

A stated alphabet (address alphabet, a FASTA alphabet= field or GenBank molecule type) overrides detection, and the person can override it in the paste box. Letters outside the chosen alphabet are listed as offenders with 1-based positions (offenders). Alphabets: DNA ACGTRYSWKMBDHVN, RNA ACGURYSWKMBDHVN, protein ACDEFGHIKLMNPQRSTVWYUOBZXJ.

Import detectors (Stage B)

src/apps/sequence-toolkit/import/ holds Sequence's detectors for the suite's two-stage import layer (src/lib/project/detect.ts; docs/design/project-file-and-grid-contracts.md §4; architecture record §5, the CTO's rulings S4a to S4e, Q15 and Q16 of 12 October 2026). Nothing in the app calls it yet: the view and the import surface are later Stage B work. Each detector reads only the first 16 KiB (HEAD_BYTES) and allocates nothing in proportion to the file; the name and media type only order the detectors.

Kind Certain Likely Cap Thread
genbank first non-blank line is LOCUS <name> <n> bp|aa|rc a LOCUS line and a FEATURES or ORIGIN line in the head 16 MiB worker
fasta first non-blank character > and the next non-empty line at least 90 percent sequence letters > alone 64 MiB worker
text never never (possible: at least 90 percent of non-space, non-digit characters are letters, no >, @ or LOCUS) 2 MiB main
dna byte 0 is 0x09, the length at bytes 1 to 4 at least 14, bytes 5 to 12 SnapGene 8 MiB worker
ab1 ABIF and the directory entry at byte 6 named tdir ABIF alone 8 MiB worker
fastq the head's first four-line record whole, bases and qualities of equal length an @ line with a + line within three lines 64 MiB worker
inputs-json the whole file in the head and its top-level keys exactly the v1, v2 or partial set its keys in the head, or a head cut inside a sequence-toolkit file's residues (a long record's residues come first) 8 MiB main

isGzip (1f 8b) is the surface's check before the detectors run; no detector claims the gzip magic, and compressed files are read in B-4. A reader reads the kind the person confirmed and never re-guesses it: text that is not FASTA is refused as not-fasta, a header read as plain letters as has-header. GenBank, FASTA and plain letters read through parseInputWith, so a file and a paste of the same text read the same. A paste under 100,000 letters is read on the main thread (readPaste) and hashed with the platform's SHA-256; every value carries its source's name, size and sha256: hash. importOutcome is the set outcome (contracts §4 rule 8): a set section takes every record as one set; a record section opens a single record and sends several to the chooser, which offers "all, as a set" only when asked. The words are in content/apps/sequence-toolkit/import.ts. A refusal (ImportProblem, types.ts) carries a code and codes and numbers only, never words: the file's name (file), a 1-based line, entry or record, an inputs field's path, an .ab1 refusal's version or type (reads), the call sets the file offers (choices), and a GenBank or .dna parser's own message; every string is bounded to 200 characters and none echoes the file's bytes (the readers' review of 21 October 2026, findings 2, 3 and 6). importText.refusals words the text and inputs readers' codes, one line a code (a test holds the list). Every worker kind reads in a worker under the one-job contract (the CTO's Q15 and Q32; src/lib/seq/reads/one-job.ts): a new worker per job, the bytes copied into buffers that are transferred, a job id and a key the answer must echo (an answer for another file is stale), the worker terminated on the answer, on cancel and after 30 s (timeout). GenBank, FASTA, .dna and a paste of 100,000 letters or more go to the import worker (src/components/tools/sequence-toolkit/import.worker.ts, marker drybench-worker:sequence-import; the job in import-job.ts, the messages in import-protocol.ts, the client in import-client.ts), which checks each kind's cap and the declared size again, hashes the bytes with the project file's sha256Hex and reads them; .dna goes through Plasmid's readSnapGene, imported. .ab1 and FASTQ go to the reads worker (below). readReadsSet reads .ab1 and FASTQ files as one set with the person's declarations (FASTQ offset 64, the edited .ab1 calls); the generic reader reads one file at offset 33 with the base caller's calls. A reads refusal (fromReads) carries the file's name, its line or directory entry, an .ab1 refusal's version or type and the call sets the file offers, for the lines in reads.ts. The reads words (refusals by code, the quality lines and reasons, the base-caller lines, editedNotUsed, the sampling lines) are in content/apps/sequence-toolkit/reads.ts; content/apps/sequence-toolkit/reads.test.ts holds every code equal to a line. inputs-json.ts is the strict reader of the legacy inputs download (architecture §5.5): the saved work (v1, v2) or the partial inputs (text, templateText, state, sourceContext), refused as not-json, not-inputs-json, unknown-key (with its path, the key itself named <unknown key>, never echoed), version-unknown or malformed (with the field readSequenceWorkspace failed on); it loads on demand, so its imports stay out of the readers' chunk. Nothing in the app calls these clients yet, so the two workers have no size budget until the commit that first wires a client into a surface (the measured sizes are in docs/progress/sequence-readers.md).

The reads module (Stage B, D-0107)

src/lib/seq/reads/ reads sequencing reads as untrusted input (architecture record §6.3; the CTO's Q21 to Q23, the CSO's Q41, Q43, Q44, Q48, B6 and B7 of 12 October 2026). It is a new engine, approved by the CEO under D-0107 (14 October 2026); it is not merged before the CSO and CTO sign off. Nothing in the app calls it yet.

  • read.ts: the normalised read (index, file, setId, name, title, bases upper-cased, quality with qualityKind "phred", "file-confidence" or "none" and every qualityReasons code, offset, peaks, traces, calls, baseCaller, editedCalls, qualifications) and the read set (id, reads, skipped, sampled, and receipt: { id, rule: "evenly-spaced-v1", cap, totalReads, files: [{ name, sha256, size, fileReads, taken }] }, the CTO's words-asks item 4, a file that gave no read listed with taken: 0). Caps: 1,000 reads a set, 20,000 bases a read, 64 MiB a file. evenlySpaced and isTaken compute floor(j × N / 1,000) in integer arithmetic (the CTO's addendum item 10). readsFromSequences makes reads without values from FASTA records or pasted calls (Q41).
  • fastq.ts: four-line records, offset 33 unless 64 is declared, never guessed; values 0 to 93 at 33 and 0 to 62 at 64, and at offset 64 every code below 64 refused as quality-below-zero (the CTO's addendum item 9); a quality line may start "@" or "+"; a repeated title must match. Blank lines (empty once the LF or CRLF is removed) are skipped only where a record's "@" line is expected, before the first record, between records and after the last, and counted for the note (blankLines: how many and the first one's line), the CSO's ruling 3 of 14 October 2026; every record still passes every check, and the counting pass skips them alike. Refusals with their 1-based line, counting blank lines: missing-header, blank-line (a blank line inside a record), whitespace-line (a line of only spaces or tabs where a record's "@" line is expected; it is not blank), empty-read, not-iupac, wrapped, missing-plus, title-mismatch, length-mismatch, quality-below-zero, quality-out-of-range, truncated, too-large, empty (no record, a file of blank lines only included), empty-title (an "@" line with no word: Biopython raises, and no name is invented) and name-too-long (a name over READ_NAME_MAX, 255 UTF-16 code units, the sequence record's limit; refused, never cut) (FASTQ_REFUSAL_CODES). A read's name is its title's first word as Python's split() gives it, Biopython's id: leading whitespace skipped, whitespace being Python's (PYTHON_WHITESPACE in alloc.ts, held against Python's own list recorded in the fixture); the counting pass measures it from the bytes with nothing allocated (firstWordUnits, a WHATWG UTF-8 decode, checked against the decoding pass by a seeded 20,000-input test), and the fixture's name cases (leading space or tab, Unicode whitespace, 255 and 256 units, characters outside the BMP) are held against Biopython 1.88 (the readers' review of 21 October 2026, finding 1). countFastq is the counting pass (every record checked, N counted, nothing allocated); decodeFastq decodes only the records the set takes, a chosen read over 20,000 bases skipped and named; readFastq is one file on its own.
  • abif.ts: .ab1 files on the .dna reader's pattern. checkAbif checks every bound before any allocation, in the CTO's order and words (ruling 1 of 20 October 2026, docs/board/memos/2026-10-20-cto-abif-bounds-phred-ln-receipts-versions-cai.md; the ABIF specification, Applied Biosystems, July 2006, read from a third-party copy, evidence note docs/science/evidence/sequence-stage-literature-2026-10-14/abif-format-specification.md): the file at most 8 MiB; the 128-byte header whole; ABIF at bytes 0 to 3; the 16-bit version at byte 4, refused unsupported-version when ⌊version / 100⌋ ≠ 1 before anything after it is read (p. 8); from the header's directory entry only its element count N (byte 18) and data offset D (byte 26), too-many-entries when N > 10,000, then out-of-bounds when N > 0 and D + 28 × N exceeds the file; the entry's name, number, element type, element size, data size and handle are not read (p. 9), and nothing is sized or looped by that data size; the name tdir sets only the detector's tier ("certain", else "likely"). Then each entry: a name of four bytes 0x20 to 0x7E (bad-entry); an element type that is current, legacy or user (1024 and above), any other refused unknown-element-type with its entry (p. 11); for a current type, the element size it fixes and a data size of count × element size (bad-entry); for a legacy or user type, size and count unchecked and the value never read (pp. 12 and 17); for every type, data of more than 4 bytes within the file (out-of-bounds); no duplicate (name, number), the header's tdir 1 included (duplicate-entry); a trace channel of at most 1,048,576 points. The current/legacy split is one table, ELEMENT_TYPES, transcribed from pp. 13 to 20 with each row's page: current byte, char, word, short, long, float, double, date, time, pString, cString; legacy thumb and bool (supported) and rational, BCD, point, rect, vPoint, vRect, tag, deltaComp, LZWComp and deltaLZW (unsupported); the CTO has not checked the transcription. bad-directory is retired. unsupported-version carries the header's version and unknown-element-type its type code (detail), for the CPO's lines (ruling 4 of 20 October 2026); the entry's tag is not read on a refusal, which allocates nothing and makes no string from the file's bytes. With these bounds Biopython 1.88's three public test files read, bases and values equal to Biopython's (a local run; the files are not committed, held for the General Counsel). Calls: PBAS2, PLOC2, PCON2 (the base caller's) by default; set 1 (edited) only by the person's choice and only when complete; never mixed. A file without set 2 is refused as no-base-caller-set when its edited set may be chosen and as no-base-calls when neither set is complete. editedCalls (and choices.edited) is absent, incomplete, identical, differs (both present and different: the CPO's editedNotUsed) or only. FWO_1 and DATA9 to DATA12 give the channel order and traces. The base caller comes from SPAC2 and SVER2 only (the CTO's exception to Q23): a string element type, 1 to 255 printable ASCII characters, else unreadable. decodeAbif allocates once a tag read; tag names of the rest are counted apart as labels; the read's name is its file name.
  • quality.ts: the CSO's Q43 rule as amended twice on 20 October 2026 (docs/board/memos/2026-10-20-cso-reads-kb-and-abif-specification.md, rulings 1 and 2; 2026-10-20-cso-kb-zero-on-mixed-calls.md). KB_DOCUMENTATION_HELD is true: the maker's documentation of the KB scale is held as the evidence note docs/science/evidence/sequence-stage-literature-2026-10-14/thermo-kb-basecaller-qv.md (sources fetched 14 October 2026). The base caller's set is read as quality scores, Q = −10 log₁₀ P(error), only when SPAC2 is exactly "KB" or "KB.bcp" (namesKbBaseCaller, case-sensitive; SVER2 is shown and never decides) and every value lies within KB_QUALITY_RANGE, 1 to 99, except that a 0 is accepted on a call in MIXED_OR_N, the ruling's explicit list R, Y, S, W, K, M, B, D, H, V and N (a mixed-base call or N; one public KB 1.2 file holds seven such 0s; any other letter, a gap or U included, is no exception, and a test runs every single-byte call). An accepted 0 is stored as 0 and enters trimming as 0; no error probability is derived from it, and such calls never count as support. abifQuality takes the calls with the values and gives the kind and every reason that applies, in a fixed order: edited (edited calls chosen that differ from the base caller's), not-named, unreadable, not-documented (a name not in a form this app has checked), over-range (a value outside 1 to 99 that is not a 0 on a mixed-base call or N); no values gives "none". Its tests and the abif-zero-* fixture files hold Q43's seven cases, the range files and the CSO's eight tests for the 0 (the 3730 pattern trimmed by the generator's own window mean, version independence, a 0 on a pure base, on an N, with a 100 or a 99 elsewhere, every call mixed at 0 trimmed to nothing, the 310 pattern with both reasons), the CSO's match and no-match names, and the SPAC2 strings of three public files read with Biopython 1.88's abif_raw. The words are in reads.ts: the CPO's not-documented, readout headers ("Quality score" for an admitted read, otherwise "Confidence value from the file"; CSV quality_score and file_confidence) and traceQualityLimit; over-range and zeroLimit interim until the CPO re-fits them for the 0. FASTQ values stay 0 to 93 at offset 33; no Phred+33 writer exists in Stage B (the CTO's ruling 2 of 20 October 2026: the cap is built with the first writer, in phred33.ts).
  • set.ts: readSet reads .ab1 and FASTQ files as one set: the files ordered by name in code points, then by the position given (byCodePoint, setOrder); a counting pass over every file; the reads at floor(j × N / 1,000) over the files together decoded; a refused file refuses the set, naming it. Each receipt file entry also names its skipped reads (skipped: index, name, reason), so chosen = taken + skipped can be checked (the CSO's ruling 6), and gives its blank lines' note (blankLines); both are present only when non-empty.
  • alloc.ts: the only place in the module that creates typed arrays or strings from a file's bytes, and it counts them (Q22). module.test.ts fails on a typed-array constructor, new ArrayBuffer, TextDecoder or String.fromCharCode anywhere else in the module; every refusal case asserts zero allocations.
  • reads-job.ts, reads-protocol.ts, reads.worker.ts (marker drybench-worker:sequence-reads) and reads-client.ts: the reads worker under the one-job contract; a request is a set id and its files (at most 1,000), one transferred buffer each; the job re-checks each file's cap and declared size, hashes each with the project file's sha256Hex (the receipt's hash, equal to its originals[] entry), reads the set with the person's declarations and transfers the reads' arrays back. Read placement with the DNA aligner joins this worker later; it is not this module's.
  • trim.ts: end trimming as a setting on a copy, never on the read: from each end while the mean quality of a 10-base window is below 20 (integer sums, so no rounding), or none; a read without quality scores (including .ab1 values not read as quality scores) is not trimmed and says so.

The read check's counting module (B-4, in progress)

src/lib/seq/readcheck/ counts placed reads against a reference under the CSO's rulings on the read-check fixture plan (14 and 20 October 2026; the plan is docs/progress/sequence-readcheck-fixture-plan.md). It is pure and does not import the aligner, whose boundary admits only its worker. Each read arrives with the aligner's per-position summary in the CTO's columnar shape (the CTO's ruling 4 of 20 October 2026; types.ts, PositionsSummary): refStart, length and crossesOrigin, a kind Uint8Array (paired, deleted or variable across every equally scoring alignment) and a read Int32Array (the aligned read index, or −1), the common insertions (the same two flanking pairs in every alignment, the CSO's ruling 2.6) and the varying-end ranges, from the read's first common pair to its last. A pure src/lib/seq/align/positions.ts, written by the aligner builder from its marked graph, will produce it on read-check requests only; until then the module runs on hand-built summaries. Nothing computes these states from listed alignments. Nothing in the app calls it yet. Its reference fixture, readcheck-v1 (pipeline/sequence-toolkit/read_check_reference.py, 20 October 2026), is generated and published (20 October 2026, write-once from here); fixture.test.ts runs countReads on the oracle's per-position summaries and requires the oracle's arrays, listings and totals on all 43 reviews that are not refused.

  • count.ts: countReads(reference, reads, settings). For every position it counts aligned, supporting (A, C, G or T equal to the reference, at Phred Q ≥ 20 by default) and disagreeing reads. Counts, never verdicts. A position paired the same way everywhere is supporting, disagreeing (listed as a substitution) or aligned only (listed as an ambiguous column when either letter is outside A, C, G and T). A position deleted everywhere is disagreeing; each run is listed once with its two flanking read bases and their Q values, which are never combined. A position that varies between alignments is aligned only; each run is listed once as a variable stretch. Common insertions are listed and totalled apart, and a varying end and overhang go on the read's row. A read that is not placed is listed under its own state and counts nothing; "no acceptable placement" is one state and one total, with its reason on the row and a total by reason (no bases left after trimming, too short after trimming, no alignment scoring above 0). Read positions are shown as supplied (Q48), and minus-strand listings carry the base on the reference strand. Totals: positions counted and not counted, aligned without support, insertions, variable stretches, ambiguous columns, reads by state, and reads aligned without quality scores.
  • junctions.ts: junctionsFromRecord lists the junctions. Designed joins are always junctions and CDS boundaries are by default. Other features' boundaries count only when selected. Never junctions: source, old_sequence, misc_difference, variation, gap, assembly_gap, a feature over the whole record, or a fuzzy end. The origin inside a feature written across it is not a junction either. Sources at one boundary merge into one junction. junctionWindow gives p − j + 1 .. p + j, across the origin on a circle and clipped at a linear end (and said so). junctionCoverage marks a junction covered when every window position has at least t supporting reads. Otherwise it lists maximal runs in window order, "no read counted" and "below the threshold" (with the supporting count, which starts a new run when it changes).

Accession-only pastes

A paste that is only an accession (UniProt, RefSeq or GenBank pattern in ACCESSION_ONLY, views/workbench.tsx) is not computed and starts no request. A UniProt accession offers a link to Protein; others show "Fetching a record from NCBI is not in this version".

Operations

Each subsection names the engine function, the parameters and defaults, and the refusals. "Applies to" follows applies() in model-core.ts.

Reverse complement

reverseComplement, complement, reverse in src/lib/seq/revcomp.ts; applies to DNA and RNA. The complement table maps A↔T, C↔G, R↔Y, S↔S, W↔W, K↔M, B↔V, D↔H, N↔N, -↔-, U→A; any other letter becomes N. For RNA, T in the output is written U. The panel shows the reverse complement at full width, then the complement (same direction) and the reverse side by side from md. No method chip. Each SequenceBlock (panels/parts.tsx) writes numbered lines of ten-letter blocks and fits the line to its box: lettersPerLine in model-core.ts takes the measured box width, the mono advance (a ten-character probe, re-measured when the web font arrives) and the position column, and keeps whole blocks, 10 to 60 letters, so a half-width panel wraps at 30 or 40 letters rather than clipping its lines at the edge (UI design review of 11 October 2026, UI-20; known issue REL-17). The server and first paint use 60; a box narrower than one block still scrolls sideways. Copy always takes the whole sequence.

Translation and genetic codes

translate, translateWith, frameSequence in src/lib/seq/translate.ts; codon lookup in src/lib/seq/codes.ts.

  • Tables: the 27 tables of NCBI gc.prt version 4.6 (ids 1 to 6, 9 to 16, 21 to 33), generated into codes-data.ts by pipeline/sequence-toolkit/build-codes.mts from the saved file (sha256 2aecbdd0…, fetched 2026-10-06, Last-Modified 10 June 2020). Codon index = 16·b1 + 4·b2 + b3 with T 0, C 1, A 2, G 3.
  • Frames: +1 to +3 read the strand from position 1, 2 or 3; −1 to −3 read the reverse complement from position 1, 2 or 3; a trailing partial codon is dropped.
  • Ambiguous codons are expanded through every base the IUPAC codes allow (U read as T): one amino acid if all agree, * if all are stops, else X. A gap or unknown letter gives X.
  • Options: toStop (ts=1) stops before the first stop; initiatorAsMet (im=1) reads the first codon as M when it is one of the table's start codons. Defaults: table 1, frame +1, both off.
  • Context-dependent stops: a codon whose sncbieaa mark is * while its amino acid is not (tables 27, 28 and 31) is in contextDependentStops. For those tables toStop throws in the engine; translationOf returns an empty protein with a warning instead.
  • The "Use the protein" action moves the translation into the paste as a protein FASTA record and switches to protein MW and pI.

Six frames

sixFrames returns all six translations with the selected table (not toStop); summariseFrames counts stops per frame and finds the longest stop-free stretch. Inputs of 100 kb and more run in the worker.

Open reading frames

findOrfs in src/lib/seq/orf.ts, NCBI ORFfinder 0.4.3 conventions as the CSO set them on 6 October 2026:

  • Start codons: ATG only (default) or every start codon of the table (alt=1).
  • An ORF begins at the most upstream permitted start after the previous in-frame stop and ends with the table's stop codon, included in end and lengthNt.
  • Minimum coding length 75 nt (ORF_MINIMUM_CODING_NT), counted from the start codon to the codon before the stop; settable 3 to 100,000 (min).
  • An ORF with no stop before the sequence ends runs to the last complete codon, has stopCodon: null and goes to a separate list.
  • A start that is the first complete codon of its frame is flagged startsAtFrameStart (it may begin before the sequence) and is translated as an internal codon (CTG gives L); other starts read M.
  • Nesting is decided for every ORF, with or without a stop: an ORF whose span, stop included, lies inside a strictly larger span of another ORF on either strand and any frame is nested, hidden by default and counted. The comparison is a sorted O(n²) pass.
  • Coordinates are 1-based on the given strand; minus-strand ORFs have start greater than end. lo/hi give the plus-strand span.
  • Tables with context-dependent stops are refused with refusedReason.
  • The sequence is scanned as linear. A circular record's ORFs across the origin are not found.
  • "Translate this ORF" moves the ORF's own nucleotides (orfNucleotides, reverse complemented on the minus strand) into the paste and switches to Translate.

GC percent

gcContent in src/lib/seq/gc.ts: GC = (G + C + S) / (A + C + G + T + U + S + W). R, Y, K, M, B, D, H, V, N, X, gaps and anything else are excluded from both counts and reported. Nothing countable gives percent: null, never 0 percent. The comment states this is Biopython's gc_fraction default ambiguity rule. No method chip.

Nucleic acid molecular weight

nucleicAcidMass in src/lib/seq/dna-mw.ts; sums an exact elemental formula with the CIAAW 2021 atomic weights (src/lib/units/formula-mass.ts, table id ciaaw-2021) and rounds once at display.

Residue (monophosphate less water) DNA RNA
A C10H12N5O5P C10H12N5O6P
C C9H12N3O6P C9H12N3O7P
G C10H12N5O6P C10H12N5O7P
T / U C10H13N2O7P C9H11N2O8P

Monophosphate formulas are from PubChem (dAMP CID 12599, dCMP 13945, dGMP 135398597, dTMP 9700, read 6 October 2026).

linear, 5'-OH (default):  M = sum(residues) + H2O - HPO3
linear, 5'-phosphate:     M = sum(residues) + H2O
circular:                 M = sum(residues)              (no end groups)
double-stranded:          the same form applied to both strands and added

The default card is single-stranded, linear, 5′-OH and 3′-OH, neutral free acid; the copy says delivered oligos are usually sodium salts and weigh more. Any letter other than A, C, G, T (U for RNA), including ambiguity codes and gaps, refuses the calculation with each offender named.

findMotif in src/lib/seq/motif.ts. A nucleotide motif uses IUPAC codes, matching when the code sets intersect (codeMatches, T and U one base); it is searched on the given strand and, for DNA unless both=0, on the reverse complement. A protein motif matches letter for letter with X as a wildcard (B, Z and J are not expanded). Matches may overlap; minus-strand matches are reported with start greater than end. The search stops at 5,000 matches (MOTIF_MATCH_CAP) and reports capped. The motif is at most 60 letters (address check). The CSV lists the first 500 matches.

Protein molecular weight, pI and ε280

sequenceProperties, proteinMass, extinction280 in src/lib/seq/protein-mass.ts; isoelectricPoint in src/lib/seq/pi.ts. These are the same functions Protein and Calc use.

Molecular weight: the default residue table is ProtParam's average residue masses (DEFAULT_RESIDUE_TABLE = "protparam", methods-protein.ts), plus water 18.01524 Da once. A second table, ciaaw-2021, sums each residue formula with the CIAAW 2021 weights; it is not selectable in the UI. Which table the suite uses was recorded as a decision for the CEO; the code defaults to protparam. B, Z, X and J are skipped and reported; U (selenocysteine) and O (pyrrolysine) are priced.

Residue Mass (Da) Residue Mass (Da)
A 71.0788 M 131.1926
R 156.1875 F 147.1766
N 114.1038 P 97.1167
D 115.0886 S 87.0782
C 103.1388 T 101.1051
E 129.1155 W 186.2132
Q 128.1307 Y 163.176
G 57.0519 V 99.1326
H 137.1411 U 150.0388
I, L 113.1594 O 237.3018
K 128.1741 water 18.01524

Isoelectric point: Henderson–Hasselbalch net charge with the Bjellqvist pK set as ProtParam applies it, bisection over pH 0 to 14 until the interval is under 0.0001, reported to 2 dp.

N-terminal pK by first residue: A 7.59, M 7.0, S 6.93, P 8.36, T 6.82, V 7.44, E 7.7, otherwise 7.5
C-terminal pK: 3.55 whatever the last residue
Positive side chains: K 10.0, R 12.0, H 5.98
Negative side chains: D 4.05, E 4.45, C 9.0, Y 10.0
charge(pH) = 1/(1+10^(pH-pK_N)) - 1/(1+10^(pK_C-pH))
           + sum_pos n_i/(1+10^(pH-pK_i)) - sum_neg n_i/(1+10^(pK_i-pH))

The published Bjellqvist table gives C-terminal values of 4.55 (Asp) and 4.75 (Glu); ProtParam's own output follows 3.55 for every residue, which the fixtures P08, P21 and P22 separate. The code follows ProtParam. The brief records the 1994 N-terminal values as "from memory"; their agreement rests on the 22 ProtParam fixtures, not on a recorded reading of the paper.

ε280 (Pace et al. 1995), M⁻¹ cm⁻¹:

reduced  = 5500·nTrp + 1490·nTyr
cystines = reduced + 125·floor(nCys / 2)
A280 of 1 g/L = cystines / MW

Tags, protease sites and cleavage

scanConstruct in src/lib/seq/constructs.ts finds tags and protease sites by regular expression on the one-letter sequence.

| Tag | Motif | Protease site | Motif (cut at |) | | --- | --- | --- | --- | | His6 | H{6,10} | TEV | ENLYFQ| followed by G or S | | FLAG | DYKDDDDK | Thrombin | LVPR| followed by GS | | HA | YPYDVPDYA | Factor Xa | I[ED]GR| | | c-Myc | EQKLISEEDL | HRV 3C (PreScission) | LEVLFQ| followed by GP | | Strep-tag II | WSHPQFEK | Enterokinase | DDDDK| | | V5 | GKPIPNPLLGLDST | SUMO (Ulp1) | No motif; never detected | | S-tag | KETAAAKFERQHMDS | | | | Avi | GLNDIFEAQKIEWHE | | | | T7 | MASMTGGQQMG | | |

The proposed cut is the first site if its cut position lies in the N-terminal half (the product keeps what follows), otherwise the last site (the product keeps what precedes). Numbers after cleavage are shown only when the person confirms the cut (cut=1). A FLAG tag also matches the enterokinase site, which is its real cleavage site. The method is marked "unit-tested", not "verified"; its check is that the His6-TEV fixture construct (P13) cleaves to the untagged fixture sequence (P14).

Peptide SMILES for Molecule

peptideSmiles in src/lib/seq/peptide.ts writes a linear peptide of L-amino acids N to C as N[C@@H](R)C(=O) per residue (glycine NCC(=O), proline N1[C@@H](CCC1)C(=O)), closed with O: free amine and acid, neutral, cysteines as thiols, no disulfides. One trailing * is removed and reported; X, B, Z, J (ambiguous), U, O (the oracle cannot build them), internal stops, gaps and dots are refused with positions. The SMILES travels to Molecule only while it is at most 2,000 characters (PEPTIDE_SMILES_MAX). The panel offers the SMILES of the full sequence, not of the cleaved product.

Oligo Tm

oligoMelting in src/lib/seq/tm.ts. Inputs are cleaned of whitespace and upper-cased only; any character other than A, C, G or T (ambiguity codes, U, inosine, gaps) refuses the Tm with each offender named. Fewer than 2 nt is refused.

Nearest-neighbour sums (SantaLucia 1998, Table 2), ΔH° kcal/mol and ΔS° cal K⁻¹ mol⁻¹, for a perfectly matched duplex:

Stack ΔH° ΔS° Stack ΔH° ΔS°
AA/TT −7.9 −22.2 CT/GA −7.8 −21.0
AT/TA −7.2 −20.4 GA/CT −8.2 −22.2
TA/AT −7.2 −21.3 CG/GC −10.6 −27.2
CA/GT −8.5 −22.7 GC/CG −9.8 −24.4
GT/CA −8.4 −22.4 GG/CC −8.0 −19.9
Initiation, terminal G·C (per end) +0.1 −2.8 Initiation, terminal A·T (per end) +2.3 +4.1
Symmetry (self-complementary) 0 −1.4
Tm(1 M) = 1000·ΔH° / (ΔS° + R·ln(k)) − 273.15,   R = 1.987 cal K⁻¹ mol⁻¹
primer in excess (default):  k = C            (SantaLucia and Hicks 2004: C_A − C_B/2, C_B → 0)
equal strands:               k = C/4, or k = C with the symmetry term for a self-complementary oligo

Both models are always computed; cm=equal makes equal strands the headline. Defaults (DEFAULT_TM, model-tm.ts): oligo 250 nM, Na⁺ 50 mM, Mg²⁺ 0, dNTP 0. The engine accepts K⁺ and Tris ([Mon⁺] = Na⁺ + K⁺ + Tris/2), but the UI and address expose only Na⁺, so K⁺ and Tris are always 0 in the app. The panel notes that lengths over 40 nt are beyond the independent fixtures.

Salt corrections

The correction is chosen by saltCorrectionFor: Owczarzy 2008 whenever Mg²⁺ > 0, otherwise Owczarzy 2004 unless sc=santalucia selects the SantaLucia 1998 entropy correction. With Mg²⁺ present the 1998 choice is ignored.

Owczarzy 2004 (equation 22), [Mon⁺] in mol/L, f_GC the GC fraction:

1/Tm = 1/Tm(1 M) + (4.29·f_GC − 3.95)·1e-5·ln[Mon⁺] + 9.40e-6·ln²[Mon⁺]

Owczarzy 2008 (equation 16 with 18 to 20), free Mg²⁺ and [Mon⁺] in mol/L, N the length:

free Mg: root of Ka·x² + (Ka·D − Ka·M + 1)·x − M = 0,  Ka = 3e4 M⁻¹, D = dNTP, M = total Mg
R = sqrt([Mg_free]) / [Mon⁺]   (undefined when [Mon⁺] = 0)
R < 0.22:          use the 2004 equation
0.22 ≤ R < 6:      adjusted constants
  a = 3.92e-5·(0.843 − 0.352·sqrt[Mon⁺]·ln[Mon⁺])
  d = 1.42e-5·(1.279 − 4.03e-3·ln[Mon⁺] − 8.03e-3·ln²[Mon⁺])
  g = 8.31e-5·(0.486 − 0.258·ln[Mon⁺] + 5.25e-3·ln³[Mon⁺])
R ≥ 6 or no Mon⁺:  original constants a 3.92e-5, d 1.42e-5, g 8.31e-5
b −9.11e-6, c 6.26e-5, e −4.82e-4, f 5.25e-4
1/Tm = 1/Tm(1 M) + a + b·ln[Mg] + f_GC·(c + d·ln[Mg]) + (e + f·ln[Mg] + g·ln²[Mg]) / (2·(N − 1))

The card shows R, the regime and free Mg²⁺, and a near-boundary note when R is within 0.198 to 0.242 or 5.4 to 6.6 (CSO judgement). The regime is never smoothed across the boundaries.

SantaLucia 1998 (equation 7, as entropy): ΔS° + 0.368·(N − 1)·ln[Mon⁺].

Condition bounds (CONDITION_BOUNDS, tm-bounds.ts; CSO ruling of 10 October 2026 on audit M4) refuse rather than clamp: [Mon⁺] over 2,000 mM, Mg²⁺ over 1,000 mM, dNTP over 100 mM, oligo outside 0.001 nM to 1,000,000 nM, and [Mon⁺] under 1 mM while free Mg²⁺ is under 0.1 mM. Non-finite or negative values and zero oligo concentration are refused, and a non-finite Tm is refused.

Inside the bounds a result carries outsideFit notes without changing the Tm: the 2004 equation (alone, or as the 2008 R < 0.22 branch) outside 20 to 1,020 mM [Mon⁺] (Owczarzy 2004 measured 69 mM to 1.02 M; 20 to 69 mM accepted by CSO ruling), and the 2008 divalent branch at free Mg²⁺ outside 0.5 to 600 mM. That 0.5 to 600 mM range is "recalled, not yet read against the paper" and the card labels it Unverified (open question Q-24). The 1998 entropy correction has no range note because no range has been read. The limit line from tmLimits is shown with every Tm in the workbench, CSV and recorded result.

Primer check: pair, annealing temperature and structures

primerCheckOf (model-primer.ts) computes the Tm of the forward primer (the paste, which must be one DNA record of A/C/G/T) and of the reverse primer (o2) on their annealing parts, and the pair: ΔTm = |Tm₁ − Tm₂|; Ta = lower Tm − 5 °C (ANNEALING_OFFSET_C), labelled a rule of thumb. Hairpins, self-dimers and the cross-dimer use the whole oligos, 5′ tail included (primerCheckInputs, the single definition used by the panel, the CSV rows and the recorded result since the C1 fix of 10 October 2026). Structures use the Tm conditions' Na⁺, Mg²⁺ and dNTP. MAY_FORM_KCAL (−5 kcal/mol) is the vendor rule of thumb used in the structure cards' wording.

Hairpin and dimer ΔG°37

hairpin, dimer, scoreHairpin, scoreDimer in src/lib/seq/structure.ts, parameters in src/lib/seq/structure-params.ts (each table transcribed from the paper or database named beside it on 6 October 2026). Oligos are A/C/G/T only and at most 100 nt (STRUCTURE_MAX_NT).

Algorithm. Hairpins: a dynamic programme over closing pairs (i, j) with a hairpin loop of at least 3 bases, stacks, bulges and internal loops of at most 30 unpaired bases in total (MAX_LOOP), and an exterior-loop pass that can place several stems along the strand with dangling ends or terminal mismatches. Multibranch loops are not modelled. Dimers: a dynamic programme over pairs with the second strand reversed, one duplex with internal loops and bulges up to 30, and end terms. Complexity is O(n²·MAX_LOOP²); the 100 nt cap bounds it. The structure reported is the one with the lowest ΔG°37; none is reported unless the best ΔG°37 after salt is below 0.

Energies, kcal/mol at 37 °C (T = 310.15 K), ΔG = ΔH − T·ΔS/1000:

Term Value and source
Watson-Crick stacks SantaLucia 1998 Table 2 (the Tm table), converted to ΔG°37
Duplex initiation, once per dimer, none for a hairpin From ΔH +0.2, ΔS −5.7 (SantaLucia and Hicks 2004 Table 1): 1.968
Terminal A·T penalty, each helix end From ΔH +2.2, ΔS +6.9: 0.060
Hairpin loop initiation (n = 3 to 30) Table 4: 3 3.5, 4 3.5, 5 3.3, 6 4.0, 7 4.2, 8 4.3, 9 4.5, 10 4.6, 12 5.0, 14 5.1, 16 5.3, 18 5.5, 20 5.7, 25 6.1, 30 6.3
Bulge initiation Table 4: 1 4.0, 2 2.9, 3 3.1, 4 3.2, 5 3.3, 6 3.5, 7 3.7, 8 3.9, 9 4.1, 10 4.3, 12 4.5, 14 4.8, 16 5.0, 18 5.2, 20 5.3, 25 5.6, 30 5.9
Internal loop initiation Table 4: 3 3.2, 4 3.6, 5 4.0, 6 4.4, 7 4.6, 8 4.8, 9 4.9, 10 4.9, 12 5.2, 14 5.4, 16 5.6, 18 5.8, 20 5.9, 25 6.3, 30 6.6
Unlisted loop lengths ΔG(n) = ΔG(x) + 2.44·R·T·ln(n/x), x the next shorter listed length, R = 1.9872e-3; lengths over 30 take 30
Internal-loop asymmetry 0.3 per base of
Internal single mismatches (1×1 loops) Table 2, sum of the two increments (MISMATCH_DG37)
Dangling ends Bommarito et al. 2000, as SantaLucia and Hicks 2004 Table 3 reprints it (DANGLING_ENDS, ΔH° and ΔG°37; only ΔG°37 is used)
Hairpin-loop terminal mismatches (loops ≥ 4) NNDB DNA table dna_hairpin_terminal_mismatch_dg.txt (ΔG°37 only), saved in pipeline/sequence-toolkit/nndb/; Watson-Crick entries (0.0 in the file) are null

Conventions inferred from ntthal's output on the fixtures and its manual (recorded on the methods page and in structure.ts): a 3-base hairpin loop takes the terminal A·T penalty when closed by A·T (the paper's +0.5 is noted, not used); in a 4-base loop an unformable Watson-Crick first "mismatch" takes the stack instead; elsewhere a Watson-Crick terminal mismatch is two dangling ends; terminal mismatches at duplex ends and inside internal loops are the sum of two dangling ends; destabilising dangling ends at helix ends are left off; complementary flanking bases earn no dangling credit; a bulge of 1 adds the stack across it, longer bulges add the A·T penalties of both closing pairs; no symmetry correction for a self-dimer. Special triloop and tetraloop bonuses are not included, so hairpins with GNA or GNRA loops are estimated less stable than they are; the card says so.

Salt: ΔS° gains 0.368·k·ln[Na⁺]eq, applied as −T·0.368·k·ln([Na⁺]eq / 1000 mM)/1000 kcal/mol, with k the number of pairs less one per helix, charged per step inside the search. [Na⁺]eq = Na⁺ + 120·sqrt(max(0, Mg²⁺ − dNTP)) mM (von Ahsen et al. 2001, as Primer3's manual states). This is not the Owczarzy treatment used for Tm, and dNTP chelation here is a subtraction, not the Ka equilibrium. ntthal shows one salt step fewer for a hairpin whose stem includes the last base; this engine counts every pair, a difference of up to 0.37 kcal/mol on the fixtures. Non-finite or negative conditions, or [Na⁺]eq not above 0, are refused.

Gate (STRUCTURE_GATE, methods-structure.ts; CSO ruling of 6 October 2026), recomputed by structure.test.ts: (a) ntthal's own structure rescored by this engine is within the tolerance of ntthal's ΔG on every case with a structure, the tolerance being the measured maximum rounded up to 0.1 and never above 0.5; (b) the engine's ΔG is at or below that rescore; (b2) the search's minimum equals an exhaustive minimum on 200 seeded random oligos (hairpins 10 to 14 nt, dimer pairs 5 to 8 nt); (c) no-structure cases report nothing below −0.1. On 10 October 2026 the test logged: hairpins 15 fixtures, 13 with a structure, largest gap 0.366 (H02), tolerance 0.4, structures coinciding with ntthal's on 6 of 13; dimers 16 fixtures, 14 with a structure, largest gap −0.081 (X02), tolerance 0.1, coinciding on 13 of 14. The method is marked verified only while both engines pass; otherwise its cards are withheld.

Near-match binding diagnostics

bindingSites in src/lib/seq/motif.ts: exact and near matches of each primer (as entered, without tail) on a linear template, both strands, with up to mm mismatches (default 2, 0 to 3) outside a 3′ anchor of 6 bases that must match exactly; IUPAC codes match by set intersection; capped at 5,000. Sorted by mismatch count. The panel labels this a linear diagnostic that does not cross a circular origin and does not determine the exact product. productBetween (first forward site to the first reverse site) is no longer used by the workbench, but the primer-check task page's worked example still uses it.

Predicted PCR product (exact-pair-v1)

predictPcr in src/lib/seq/pcr.ts; adapter readPcrTemplate in model-pcr.ts; record in src/apps/sequence-toolkit/pcr-product.ts; panel pcr-product-panel.tsx; design note docs/design/pcr-product-architecture.md.

Inputs: the template (plain DNA or one FASTA record, A/C/G/T only; topology from ptop or a header token circular=true, alphabet=dna-circular or topology=circular), the forward annealing part (the paste), the reverse annealing part (o2) and optional separate 5′ tails (ftail, rtail). Nothing is inferred about where a tail ends.

Limit (PCR_LIMITS) Value
Template 50,000 bases
Annealing part 8 to 200 bases
Tail 0 to 200 bases
Sites per primer, both strands 100
Candidates 100

Algorithm: every exact occurrence of each annealing part and of its reverse complement (with circular wrap of one primer length) is a site, with 1-based inclusive coordinates from the primer's 5′ to 3′ end. Each opposite-strand pair facing inward is a candidate when the footprints do not overlap (templateSpan ≥ forward length + reverse length); overlapping pairs are counted, not built. A circular product traverses at most one turn. The product is oriented from the supplied forward primer:

product = forward tail + oriented template span + reverse_complement(reverse tail)

Status is none, unique or ambiguous. Exceeding a site or candidate limit refuses the whole prediction; only-overlapping pairs give overlapping-sites. Candidate ids encode both directed sites and are stable only within identical input; the panel keys its selection on the full input JSON and never auto-selects among several. Tm uses the annealing parts; structures use the full oligos.

The record (PcrProductRecord, predicted: true, topology: "linear") stores the normalised inputs, the selected candidate, counts, names, creation time and PCR_PRODUCT_LIMITATION. readPcrProduct recomputes and refuses unknown versions or altered outputs. Exports: FASTA with predicted=PCR, GenBank with two primer_bind features and the full template and primers in COMMENT lines (template feature annotations are not transferred), and provenance JSON (format: "drybench-pcr-product"). "Save product" creates a mine:construct record holding a SequenceWorkspaceData payload; "Open in Plasmid" saves and then navigates by document navigation (window.location.assign) so the outgoing fragment writer cannot overwrite Plasmid's mine key. Products over 50,000 bases cannot be opened in Plasmid. Saving requires Workspace write access; downloads do not.

Primer design (bounded-flanks-v2)

designPrimers in src/lib/seq/primer-design.ts, run in primer-design.worker.ts from primer-design-panel.tsx; the primer-design operation and its page (/sequence/primer-design, island islands/primer-design.tsx, budget sequence-island-primer-design), offered for an A/C/G/T DNA template of at most 50,000 bases (one DNA record pasted; applies in model-core.ts). Method record PRIMER_DESIGN_METHOD (src/lib/seq/methods-design.ts, id primer-design-bounded-flanks, version bounded-flanks-v2), METHODS.primerDesign verified, METHODS_BY_OP["primer-design"] = primerDesign, tm, salt2004, salt2008, pcrProduct, structure; the design's target and constraints are the panel's fields and not in the address (only the Tm conditions travel, OPERATION_KEYS). The island shows the search alone and hands a pair to the workbench's primer check by link; the collection (retain, import, export, reuse from a named version) is the workbench's. It is a bounded, template-local enumeration written for Drybench, not a port of Primer3 (docs/design/pre-release-app-depth/molecular.md M1). A design worker whose code fails to load (an error before it answers) is started once more; a second failure shows the panel's error line, never an empty result (REL-27).

  1. Check inputs: template, topology, target and excluded regions (1-based inclusive; a region with end before start only on circular templates; at most 100 exclusions), flank 0 to 200, annealing length 8 to 40, product 16 to n + 400, Tm 0 to 100, ΔTm 0 to 30, GC 0 to 100, tails A/C/G/T up to 200.
  2. For each side, enumerate windows at gap 0 to flank from the target and lengths min to max (forward ends before the target, reverse starts after it). Reject windows outside a linear template, overlapping the target or an exclusion, outside the GC range, or with a refused or out-of-range Tm (oligoMelting with the panel's Tm conditions). Each rejection is counted.
  3. Rank windows by |Tm − target Tm| + 0.2·|length − target length|; keep the best 24 per side (perSide).
  4. Pair them; reject spans larger than the template, products (tails included) outside the size range, and ΔTm above the limit. Rank pairs by total = (|Tm_F − T*| + |Tm_R − T*|) + 0.2·(|len_F − L*| + |len_R − L*|) + |Tm_F − Tm_R|.
  5. Check the best 24 pairs with predictPcr for an exact product at the chosen sites; return the first 5 that pass (returnedPairs), each with its exact site and product counts.
  6. Score hairpins, self-dimers and the cross-dimer of the tailed oligos, shown and not used as pass/fail.

Site-directed mutagenesis primers (sdm-primers-v1)

designMutagenesisPrimers in src/lib/seq/sdm-primers.ts, called from mutagenesis-primers-panel.tsx; the mutagenesis-primers operation and its page (/sequence/mutagenesis-primers, island islands/mutagenesis-primers.tsx, budgets sequence-island-mutagenesis-primers and sequence-mutagenesis-results), offered for one A/C/G/T DNA record of at most 50,000 bases (applies in model-core.ts, the same rule as primer design). Method record SDM_PRIMERS_METHOD (src/lib/seq/methods-sdm.ts, id sdm-primers-two-schemes, version sdm-primers-v1), METHODS.sdmPrimers verified, METHODS_BY_OP["mutagenesis-primers"] = sdmPrimers, tm, salt2004, salt2008. The change and the scheme travel in the address (mk, mp, mb, ml, ms in OPERATION_KEYS) with the Tm conditions, so a link reproduces the design. Rebuilt under D-0096 (rebuild 3) and D-0090 from the CSO's contract of 17 October 2026 (docs/board/memos/2026-10-17-cso-sdm-primers-contract.md) with the CPO's placement and words (docs/board/memos/2026-10-17-cpo-mutagenesis-primers-placement.md); the removed Gemini panel was not read or restored, and the rules below are the contract's, not that code's.

The published rules are SDM_RULES in methods-sdm.ts, with SDM_REASONS beside them naming which counts each scheme can report, and the oracle types the same values into its own block:

  1. Read the template (lower case and whitespace accepted, any other character refused with each offender's position) and the change: a substitution of 1 to 3 contiguous bases, an insertion of 1 to 12 bases, or a deletion of 1 to 12 bases. A substitution that matches the template at every position is refused as no change; one that matches at some positions is a design of its outer changed bases.
  2. Build the mutant. Mutant coordinate 1 is template position 1, except for a change that crosses a circular origin, where the mutant is read from the first retained base after the changed span and the origin is reported (mutantOrigin).
  3. Overlapping complementary: enumerate every mutant window containing the change, 25 to 45 bases, and reject under the first rule that applies, counting it: boundary, length, flankShort (a matched flank below 10), offCentre (the flanks differing by more than 3), gc (outside 30 to 70 percent). The forward primer is the window and the reverse its exact reverse complement. Rank by flank imbalance, then whether both flanks are in the preferred 12 to 15 band, then GC distance from 40 to 60, then length from the midpoint, then the lowest start.
  4. Back-to-back, circular templates only: the changed bases sit within the forward primer's 5′-most six bases (an insertion's bases and a deletion's junction are themselves its 5′ end), with at least 15 matched bases 3′ of the last change and 18 to 30 matched bases in all; the partner primer is the reverse complement of the 18 to 30 mutant bases immediately 5′ of it, so the two 5′ ends abut. The pair's matched-part Tm must agree within 3 °C. Rank by that ΔTm, then mean GC distance, then the lengths from the midpoint, then the coordinates.
  5. Return the first five candidates and report how many passed every rule. The published rules bound the enumeration below the 5,000-candidate cap (945 windows and 1,248 pairs at most), so no design reaches it; the cap is reported all the same.

Every melting temperature comes from oligoMelting, and every one is of a perfectly matched duplex: the primer against the mutant it matches exactly, and its longest perfectly matched 3′ segment against the template. The melting temperature of a primer against a template it does not fully match is outside the model and is never derived, in the panel or the CSV; the panel's withheld line says so and why, beside the table. A primer whose 3′ matched flank is its whole length aligns to one contiguous run of the template, so one Tm applies to it and the table says so: that is the back-to-back partner always, and a deletion's back-to-back forward primer. A window spanning a deletion junction is not fully matched, because its two flanks are not contiguous in the template. The only Tm-decided rule is the back-to-back ΔTm; every ranking term is compared at a published precision (1e-6 °C, 1e-9 percent GC) so the order is decided between whole numbers, and the method record says that precision is the engine-to-oracle agreement and not a physical resolution. The conditions every Tm was computed at, and the salt correction's fitted range with any note that the conditions sit outside it (tmLimits, carried once per run on the result), appear under the table and in the CSV; the panel adds no condition field of its own, so the Tm surface is unchanged (the CSO's rules 16 and 19, the CPO's ruling 1 of 17 October 2026). No Tm floor or vendor protocol threshold filters a design, no annealing temperature is shown, and no cycle count, polymerase, DpnI, phosphorylation, ligation or kit appears anywhere: sdm-primers-fixtures.test.ts asserts the absence of each word in a result.

Three counts cannot be non-zero and the shape says so rather than hiding them: matched3PrimeShort, because the back-to-back enumeration starts at its published minimum, and length in the overlapping scheme, because a window of the wrong length is never enumerated; abutment counts the pair too long for the molecule, and boundary only a span off the mutant (SCI-164). The panel shows the chosen scheme's reasons only.

Checked since 17 October 2026 against pipeline/sequence-toolkit/fixtures/sdm-primers-v1.json (10 design cases over both schemes and 8 refusals, written by pipeline/sequence-toolkit/sdm_primers.py before the engine existed, one case per scheme worked by hand with a write refusal; src/lib/seq/sdm-primers-fixtures.test.ts), published under public/verification/sequence-toolkit/0.7.0/.

The result records the template SHA-256 (dnaHash), counts, ranking terms, whether the search was capped, and three limitation sentences (DESIGN_LIMITATIONS). Checked since 11 October 2026 against pipeline/sequence-toolkit/fixtures/primer-design.json (9 design cases and 4 refusals from an independent Python derivation with Biopython's Tm, one case worked by hand; src/lib/seq/primer-design-fixtures.test.ts), published under public/verification/sequence-toolkit/0.5.0/; Primer3 was run offline as a recorded comparison (docs/science/evidence/primer-design-2026-10-11/), not as the oracle. Panel defaults: target about the middle third, flank 50, length 18 to 26 (preferred 20), Tm 55 to 68 °C (preferred 60), ΔTm 3, GC 30 to 70 percent, product from max(16, min(60, n)) to max(16, n) bases, where n is the template length.

New design receipts retain each oligo's exact Tm, method/version, complete calculation conditions and fitted-range/extrapolation qualifications (tm-review.ts). Candidate cards, saved primer-set cards and order CSVs carry those qualifications. Saved primer sets with historical bounded-flanks-v1 receipts remain readable without recalculation; where no Tm evidence was retained they explicitly say it was not recorded. Receipts with bounded-flanks-v2 require the retained evidence and reject non-finite temperatures. Existing historical records and recorded outputs are not rewritten.

Primer sets

src/apps/sequence-toolkit/primer-sets.ts. A design pair can be added to a draft collection of at most 20 entries, saved with the Sequence task (payload v2). Each entry records the annealing parts, tails, Tm conditions, a binding witness (template SHA-256, length, topology, directed sites, product id, exact-pair-v1) and the design receipt. readPrimerSets is a strict reader that does not recompute. Collections can be exported as order CSV (with formula-escaping of cells) or JSON, imported from JSON (500 KB limit, new entry ids, original ids kept), and copied from a named version of another saved Sequence task. "Review this pair" loads it into the primer check and re-evaluates exact binding against the current template.

Design revision review (exact-transform-global-edit-v2)

compareSequenceRevisions in src/lib/seq/sequence-correspondence.ts, run in sequence-review.worker.ts. Inputs are two single-record A/C/G/T DNA revisions of at most 50,000 bases with at most 1,000 annotations (total annotation coverage at most 1,000,000). The earlier revision is pasted or taken from a named saved version; the later defaults to the current draft (the template in primer check, else the paste).

  • Identical residues: identity map.
  • Equal lengths: an exact circular rotation (both circular), or an exact reverse complement (rotated when circular), gives a coordinate map without edits; repeated sequence permitting several offsets is flagged and the lowest offset shown.
  • Any other change with either revision circular: unresolved, every residue unmapped.
  • Linear: common prefix and suffix are kept; the changed middle is aligned by unit-cost global edit distance with a diagonal-first traceback, capped at 1,000,000 matrix cells (beyond it, unresolved). Ties are flagged as ambiguous.
  • Annotations are compared after mapping (key, strand, positions, qualifiers, partial flags); features overlapping an edit are listed, and an edit overlapping a CDS adds a qualification.

The result carries both SHA-256 hashes and the method id and can be exported as JSON. It is not recorded as a Study result.

Selection summary

model-selection.ts: for a raw or single-FASTA paste, a text selection is mapped to 1-based residue positions using the same cleaning rule, then summarised as length, GC (gcContent) and, for nucleotide selections of 15 to 40 letters only (SELECTION_TM_LENGTHS), the Tm card's own tmOf result with the address conditions. GenBank and multi-FASTA pastes have no text-to-residue map and give no summary. Fixture: selection-hand.json (8 hand-worked cases).

Scientific audit follow-up, 11 October 2026

Revision comparisons now use exact-transform-global-edit-v2: annotation equivalence retains biological join order and the identity of each partial endpoint. Reverse-complement and exact-origin transforms still recognise equivalent annotations without inventing residue edits. Older saved comparison output remains historical data; only newly requested comparisons use the new method.

The structure card and exported/recorded structure rows explicitly distinguish rescoring Primer3's selected structure from agreement of the displayed minimum. Some audited headline energies differ from the Primer3-structure rescore by more than 3 kcal/mol (S04: 3.5586 kcal/mol). The exhaustive short-sequence search and existing rescore gate remain useful checks of this bounded implementation; neither proves experimental accuracy or agreement with Primer3's selected minimum. Special-loop and multibranch omissions remain.

Source qualification remains open (SCI-03). Owczarzy 2004's primary abstract reports 69 mM–1.02 M sodium measurements. Owczarzy 2008's primary abstract discusses typical PCR conditions but does not establish the recalled 0.5–600 mM free-magnesium fit range. The 11 October review could read the abstracts and author's abstract; the full 2008 paper returned HTTP 403. Full source/range review is therefore not claimed complete. The unsupported claim that the default extrapolation error is only tenths of a degree has been removed. The default 50 mM and 20–69 mM conditions explicitly remain extrapolations with unestablished error here; the 2008 range is visibly unverified.

scientific-audit-regressions.test.ts adds retained-Tm/history/export and annotation regressions, plus an independent 60-digit Decimal equation derivation at 10, 20, 50 and 69 mM sodium. This confirms arithmetic (including low-salt extrapolation), not empirical validity at those concentrations. Existing published fixtures and their tolerances are unchanged. Static oligo-Tm/primer-check worked examples now carry the same range qualifications as the live workbench.

External services

Sequence's registry endpoint is rest.uniprot.org (UniProt, CC BY 4.0, consent none), used only by "Find a protein sequence" (views/sequence-search.tsx):

  • Typing two or more characters offers suggestions from the suite's local protein index (suggestProteins, useCatalogs); typed queries are not sent. The index loads when the field is on screen, which the 10 October audit notes is every Sequence visit (about 3.1 MB) rather than on intent.
  • "Search UniProt" sends the query (searchProteins from src/components/tools/protein-explorer/api.ts); choosing a result fetches the full canonical entry (loadEntry). The pasted sequence is never sent.
  • prepareProteinSequence builds FASTA with alphabet=protein accession= organism= source=uniprot retrieved= release= fields. An empty editor is filled directly; existing input requires confirmation; input or ownership changed during loading blocks the automatic replacement (sameSequenceImportRevision); Back/Forward discards a pending lookup.

The UniProt client, its host declaration and CSP are shared with Protein; see Protein and Security and network.

"Verify at ProtParam" (https://web.expasy.org/protparam/) and "BLAST at NCBI" (https://blast.ncbi.nlm.nih.gov/Blast.cgi) are plain links with rel="noreferrer" and referrerPolicy="no-referrer" (sequence-box.tsx). They open the tools' entry pages; the sequence is not placed in the URL, so "uploads the sequence" refers to what the person then submits there. Sequence makes no NCBI E-utilities call; accession fetching is Plasmid's.

Integration with Objects, Studies and other apps

Registry objects: owns sequence; reads sequence, target, construct; writes sequence, construct, target; tabs on all three. Shared object mechanics are in Storage and state.

Path Implementation
Keep a sequence sequenceInput: ref seq:<alphabet>-<first 32 hex of SHA-256> of the normalised residues (sequenceRef; circular DNA canonicalised over rotations and strands); label = record name, else "DNA sequence, 333 bp"; aliases = header accessions and the lower-cased name; data = residues, alphabet, circular. Disabled when residues fail the alphabet
Keep a construct Offered when tags or sites are found: mine:construct/<uuid> with residues, alphabet, tag and site names, linked mentions to the sequence object
Save to Bench A mine:construct record whose data is SequenceWorkspaceData (see Saved work)
Inbound (objectInbound) sequence: seq, stated alphabet, name; target: id, range, and seq as protein when given; construct: mine (private only)
Payload hook (sequencePayloadFields) seq and alphabet while the encoded value fits 1,800 characters, name, UniProt id, and smiles for Molecule when the peptide SMILES fits
Cross-links (registry) Protein (id from a header accession), Calc (seq, alphabet, the protein for A280), Molecule (smiles, name)
Plasmid, out "Open in Plasmid" (DNA only, at most 50,000 bp) creates a carried mine:construct from the saved inputs (createMine(..., { as: "carry" }), D-0045) and pushes /plasmid/app#s=mine=… through the policy router; PCR products go by document navigation as above
Plasmid, in A Plasmid construct arriving by mine is rendered to GenBank text with its features (constructSequenceText, plasmidGenBank)
Other apps' saved inputs WorkspaceInputPicker ("Use a Bench input", beside the paste box's examples since the 11 October 2026 fix round, UI-16) accepts a molecular input and sets the operation to protein MW and pI or GC percent

The object tab (object-tab.tsx) shows MW, pI and ε280 for protein residues, or GC and single-stranded mass for nucleotides, and links into the workbench while the residues fit a link (otherwise to the empty workbench). The brief's tab (ORF count over 100 codons, Tm at default conditions, numbers after cleavage for constructs) is not implemented; the construct tab's title says "before and after cleavage" but it shows only the uncleaved numbers.

Exports

From the shell's Export menu (exports.ts), plus panel-specific downloads.

Item Content
Results as CSV (sequence.csv) sheetRows for the operation on screen: a paste block (description, full residues, name, accessions) then operation rows with columns operation, field, value, unit, method, version, constants table; delivered with the provenance block (sources, methods, limits, regulatory statement, canonical address) via buildTabularExport
Sequence as FASTA The sequence payload FASTA with alphabet= and accession fields; UniProt-sourced input keeps source=uniprot licence=CC-BY-4.0 credit=UniProt_Consortium source-edits=not-rechecked
Annotated GenBank annotatedSequence: the single record with its features and COMMENT lines, ending "No physical clone verification is implied."
Inputs JSON The SequenceWorkspaceData payload, offered while saved inputs exist; the share dialog's file option (shareInputs) downloads the same inputs when they do not fit a link
Print window.print()
PCR product FASTA, GenBank, provenance JSON (panel)
Primer design Order CSV, primer-set JSON, design receipt JSON (panel)
Mutagenesis primers The CSV rows (one block per primer, with the withheld line) and the method block
Revision review Exact revision review JSON (panel)

CSV rows exist for every operation; the method block lists the methods in METHODS_BY_OP for the operation. Export formats and the provenance block are shared; see Storage and state.

Provenance, method records and versions

Every engine module defines its MethodRef in a light methods-*.ts module (no engine code), so chips and task islands carry records without engines. content/apps/sequence-toolkit/methods/*.ts joins each with its verification state and sentence; content/apps/sequence-toolkit.methods.ts gathers them as METHODS.

Key Method id Version Constants table State
translation translation-ncbi-genetic-codes 1.0.0 ncbi-genetic-codes-4.6 verified
orf orf-finder-ncbi-conventions 1.0.0 verified
proteinMass protein-average-mass 1.0.0 protparam verified
pI isoelectric-point-bjellqvist 1.0.0 bjellqvist-protparam verified
extinction extinction-280-pace 1.0.0 pace-1995 verified
constructs construct-tags-and-sites 1.0.0 unit-tested
tm oligo-tm-nearest-neighbour 1.0.0 santalucia-1998 verified
salt2004, salt2008, salt1998 salt-correction-owczarzy-2004, -owczarzy-2008, -santalucia-1998 1.0.0 owczarzy-2004, owczarzy-2008, santalucia-1998-entropy verified
pair primer-pair-annealing-rule 1.0.0 unit-tested
structure hairpin-dimer-dg37 1.0.0 santalucia-hicks-2004 verified while the gate holds
dnaMass nucleic-acid-average-mass 1.0.0 ciaaw-2021 verified
peptide peptide-smiles-linear 1.0.0 verified
primerDesign primer-design-bounded-flanks bounded-flanks-v2 numerical oracle comparison; qualifications retained
sdmPrimers sdm-primers-two-schemes sdm-primers-v1 verified
pcrProduct pcr-product-exact-pair exact-pair-v1 verified
fragmentSearch exact-fragment-search exact-fragment-v1 verified
revision exact-transform-global-edit-v2 same verified

The saved-record fragment search (searchSavedFragments, src/lib/workspace/fragment-search.ts, used from the Objects library) reads each saved sequence or construct record through searchableDna: explicit DNA residues and topology, an app tag of sequence-toolkit (payload v 1 or 2, SequenceWorkspaceData, whose retained paste must reproduce the residues) or plasmid-map (v 1); anything else is counted as skipped by reason. Sequence's v2 payloads were skipped as unsupported until 12 October 2026 (MNT-200). Tombstoned, merged and carried records (a hand-off's, stored but not saved, D-0045; left out since 14 October 2026, as the Library leaves them out) are not searched.

"Verified" is the code's state name for "compared with the named oracle on the published fixtures"; the sentences in content/apps/sequence-toolkit.verification.ts give each oracle, count, tolerance and date. Primer design has the primer-design-bounded-flanks method record, currently bounded-flanks-v2. Its numerical gate explicitly compares the unchanged v1 Python oracle against the compatible v2 search arithmetic; v2 additionally retains Tm conditions and qualifications. This oracle comparison does not verify physical primer performance or a designed experiment. provenance.ts maps each constants table to a registry source id (SOURCE_OF); sequence.test.ts checks that every chip's table maps to a declared source.

Versioned examples pin SEQ_VERSION (1.1.0) and a "made on" date (2026-10-06) for display. There is one engine; no earlier method version is retained or selectable, so "same numbers next year" depends on not changing the engines and fixtures.

Task pages and versioned examples

Each operation page is a server component in tasks.marketing.tsx that computes a worked example at build time with the pure engine and renders an island from task-islands.tsx. Each island is its own next/dynamic chunk (marker drybench-task:sequence-toolkit/<id>) that imports model-core.ts and only its own operation's model; the GenBank reader and the link codec load on demand. Islands render on the server, so the page answers before scripts arrive. Under each answer: method, constants, verification sentences and selected limits.

Example Sequence Opens on Settings
insulin@1 NM_000207.3 bases 60 to 392 (333 nt, preproinsulin P01308) translate frame 1, table 1
gfp@1 UniProt P42212 (238 aa) ε280 none
lacz-alpha@1 J01636.1 bases 1 to 1,500 ORF finder table 11

sequence.test.ts holds the example sequences equal to the fixtures' inputs.

Methods and checks dialog

The methods and checks dialog (D-0085; Shared shell, Registry and content) reads content/apps/sequence-toolkit.methods-sheet.ts: one group per operation in the rail's order, with the workbench's own description and METHODS_BY_OP (moved to content/apps/sequence-toolkit.method-groups.ts; src/apps/sequence-toolkit/rows.ts re-exports it), then the revision review and the saved-sequence fragment search. Reverse complement, GC percent and motif search carry no method record, so their groups quote the docs page's entries. definition.methods.group() returns the bench store's op; the method inspector ends with "Methods and checks" at its operation.

Verification

The shared verification approach is described in Oracles and verification; the pipeline conventions in Data pipeline.

Oracle fixtures

All under pipeline/sequence-toolkit/fixtures/, read in place by the tests, each hashed in manifest.json. pipeline/sequence-toolkit/fixtures.test.ts checks every file's SHA-256 and the generating scripts' hashes.

File Cases Oracle Tolerance enforced by tests
translation.json 108 frame translations (3 sequences × tables 1, 2, 11 × 6 frames × full/to-stop) + 5 annotated CDSs Biopython 1.88 Seq.translate Exact
orf.json 18 (3 sequences × 3 tables × 2 start settings) Independent scan in reference.py, cross-checked with ORFfinder 0.4.3 and Biopython Exact, every field of every list
protein.json 22 Expasy ProtParam responses saved 2026-10-06; Biopython ProteinAnalysis as a second reference MW within 0.01 Da, pI within 0.01, ε280 exact
oligo-tm.json 45 (×2 models), 7 refusals, 3 acceptance Biopython 1.88 MeltingTemp.Tm_NN (DNA_NN3, saltcorr 5, 6, 7) Within 0.1 °C both models (the test also asserts 1e-6); regime and near-boundary exact
hairpin-dimer.json 20 (8 hairpins, 6 homodimers, 6 heterodimers) primer3-py 2.3.1 (libprimer3 2.6.1 thal), rerun with the bundled ntthal The gate above (0.4 hairpin, 0.1 dimer on rescored ntthal structures)
hairpin-dimer-probes.json 11 salt probes primer3-py 2.3.1 Included in the gate counts (7 hairpin, 4 dimer)
hairpin-dimer-extra.json 5 loop-class probes (GNRA, GNA, bulges, 2×3 loop) primer3-py 2.3.1 Reported, not gated
dna-mw.json 5 Exact rational arithmetic (Python fractions) from cited residue masses Within 0.01 g/mol, formula exact
peptide-smiles.json 101 (89 drawn, 12 refusals) RDKit 2026.03.6 MolFromSequence (peptides.py) Identical canonical SMILES; refusals exact
pcr-product.json 30 (13 refusals) exact_matches.py with Biopython 1.88 reverse complements, every window tested directly, one case by hand Exact
fragment-search.json 26 (9 refusals) exact_matches.py Exact
revision-transforms.json 10 revision-transforms.py: hand-worked maps, independent unit-cost edit distance, SHA-256 Kind, map, hashes, withheld state and annotation finding exact; edit count equals the independent distance
selection-hand.json 8 By hand Exact

The 22 proteins include insulin, GFP, ubiquitin, lysozyme, BSA, metallothionein-2, prothymosin α (pI 3.66), protamine P1 (pI 12.08), a 2,000-residue protein (CHD3) and a His6-TEV-EGFR construct before and after TEV cleavage, chosen to cover all seven N-terminal pK residues, C-terminal Asp and Glu, no Trp or Tyr, odd Cys counts and extreme pI. The 45 oligos are 15 to 40 nt, 20 to 80 percent GC, with self-complementary cases, 0 to 1,000 mM Na⁺, 0 to 3 mM Mg²⁺ and 0 to 1.2 mM dNTP, straddling both Owczarzy 2008 regime boundaries and including Na⁺ = 0. Full case tables are in pipeline/sequence-toolkit/README.md.

Pairwise DNA alignment (fixture first)

B-0's pairwise DNA aligner (the CSO's Sequence-stage memo of 12 October 2026, item 1; the CTO's memo, (a)(i)) is fixtured before its engine merges. pipeline/sequence-toolkit/align_reference.py writes fixtures/dna-align-v1.json (60 cases, 12 of them refusals), regenerated under the CSO's fixture ruling of 12 October 2026 (docs/board/memos/2026-10-12-cso-dna-align-fixture.md, section 3), the CSO's and the CTO's architecture rulings of the same day (the certified circular text, case 7j) their first readings of the engine (exact groups and wraps, regions, the placement words) and the CSO's final reading of 14 October 2026 (docs/board/memos/2026-10-14-cso-dna-align-final-reading.md, ruling 4: common on an indel, positions in the reference's numbering, refAfter, the no-paired-letter state, regions on a circle), on Biopython 1.88's PairwiseAligner with the scoring set dna-default-1 (a 15-letter IUPAC matrix: A, C, G, T +2 match, −3 mismatch; any pair with an ambiguity code 0) and open_gap_score −5, extend_gap_score −2, which is the published gap cost 5 + 2(k − 1) with no conversion. Modes: global (every end gap penalised), semiglobal-read (Biopython's four end "deletion" scores 0: end gaps free on the read only) and local. Per mode and strand the fixture records the optimum and Biopython's co-optimal count (a string above 2^53, overCap above 1,000), the state (aligned, no-alignment, no-paired-letter wherever any co-optimal alignment pairs nothing, wraps-past-one-turn, a wrap decided from the whole set and carrying only Biopython's block, no placement number), the strand (none where nothing is placed on the better strand or on either of two equal ones), strandUndetermined per mode, the shared pairs (uniquePairs, as reference positions: offset by a region, modulo n on a circle, so a pair in either copy of the certified text is one pair; computed from forward and backward path counts, summed over a node's copies on a circle, and, where enumerated, as the intersection of Biopython's set; they must agree), placementNotDetermined, noCommonColumn, the hull (on the certified circle the shortest arc holding every alignment's span, wholeCircle where they cover it; on a region, from the least to the greatest paired position in the region's own order (D3)), the placement groups (each with its hull and read interval; exact at any count by one forward pass over the optimal-path graph, and where enumerated equal to the partition of Biopython's set, group for group, with sizes and bands), the tie-order alignment with its identity (withheld at a zero denominator), readAligned as the read's span, referenceCovered, overhang (read bases past a linear reference's ends or a chosen region's edges, with at telling the two apart: penalised, but not columns, not identity, not coverage, not an insertion), and differences in reference coordinates, each with common (a substitution or ambiguity column by its own pair; an insertion or deletion when every co-optimal alignment has the same run with the same bounding columns, a free end gap adding no condition; computed from the programme and again from the enumeration), notFixed anchors where not common, refAfter counting a free leading gap (the nearest common pairs either side) and, on the minus strand, the position and letters as supplied. Up to 20,000 co-optimal alignments are enumerated for checking (listed up to 1,000, in the published tie order): the tie-order alignment must be the first of Biopython's set sorted by the published order, computed apart from the programme's traceback; and variation gives whether readAligned, referenceCovered, identity and the overhang on each side are shared, with their exact range. Each strand also records the band over every optimal path (unionBandLimited, the whole set's flag, as against each group's bandLimited), the shared 12-base words in the CTO's unit (sharedWordsOverCap above 100,000) and the CSO's seed guarantee (Q3(c) condition 1, with differencesUpTo). A chosen region (from, to; across a circle's origin allowed) is searched as a linear stretch and reported in the reference's numbering, coverage over the region. A circular reference is searched on the CSO's certified text: the reference followed by its first m − 1 + D bases, with D = m and one rerun at D = b when the check D ≥ b = ⌊(2m − S)/2⌋ fails; the kept count is Biopython's count on that text less its count from n onward, and circular coordinates leave out the free trailing gap, whose length depends on the text. Cases include the CSO's 7a to 7j: a deletion and an insertion across a circle's origin, a read inside a circle, overhang at both ends, minus-strand ambiguity codes, a homopolymer deletion on both strands (the same leftmost position), a read of N only, two placements over 1,000 bases apart, a wrap past one turn, a read with no unbroken 12-base match, and a 1,000-base read across the origin of the 6,646-base J01566.1 with one insertion and one deletion (7j, about 8.6 million cells a strand); and, after the first readings, a tie-dense circle (2,048 co-optimal alignments) where only alignments past the listing wrap, two placements on a circle with one across the origin, the CSO's strand example (ACGA against ACGT), three region cases (a linear region with overhang at the reference's end and at the region's edge, an inner region, and one across a circle's origin, global included) and a backwards region refused; and, after the CSO's final reading, the separating circle GTAC with the read TAG, a read with no paired letter, an insertion after a free leading gap and one before a circle's first letter, three placements on a periodic circle that together span more than a turn without a wrap, one group with no common pair, and two more refusals (a region covering the whole circle; a read longer than a circle, with a region); and, after the CSO's re-reading of 14 October 2026 (filed 20 October), a free end gap bounding an insertion on a circle, a region's hull inside the region, and a mixed tie where some co-optimal alignments place nothing; the script refuses to write if any of them does not show what it says. The fixture's about.environment records image "local", Python 3.11.17 and Biopython 1.88; about.crossCheck names the script's programme as a consistency check, not an independent oracle. The engine, src/lib/seq/align/ (see Engines), is held to every field of every case by align.test.ts; it runs whole up to the cell limit L (10,000,000 by default, up to 25,000,000 by the person's choice; cell-limit.test.ts runs a 15,000,000-cell table under 25,000,000, and a table the engine will not allocate is band-limited for cells) and is band-limited above it (banded acceptance off until SCI-213 is closed); a circle is searched on the certified text with its check enforced (one rerun; a failed check is "not determined"); and it is on its branch until the CSO and the CTO sign off. It has no view yet (when it has, Align lists no alignments to people, the CPO's words; the engine lists them for the oracle and the tests), so nothing in the app aligns DNA beyond Plasmid's annotation aligner (src/lib/seq/features/align.ts), which it does not touch. Align's words for the no-paired-letter state, the two region refusals and the placement items (each naming its read stretch) are in content/apps/sequence-toolkit/align.ts, verbatim from the CPO's memo of 14 October 2026, rulings 1 to 4, for the view to import. Its method record is src/lib/seq/align/method.ts; its methods entry and limits line are in content/apps/sequence-toolkit/methods/align.ts, not shown until the first surface (route (c) under D-0108; the CSO's ruling of 14 October 2026 on the aligner's sources, ruling 1); Gotoh 1982 is unread and not cited.

The read check's per-position summary (the CTO's ruling 4 of 20 October 2026), not read by the CSO and not used (her ruling 6 of 20 October 2026: present on the merge head, reached by nothing), comes from the same engine: src/lib/seq/align/positions.ts, asked for with positions: true in a request to the drybench-worker:sequence-dna-align worker (the protocol accepts true or nothing; Align never sends it, so its path, fixture and timings are unchanged). Per aligned strand whose placement is one group with a common pair, it gives the counted stretch from the first common pair to the last, each reference position there paired (with its read position), deleted or variable, the common insertions and the varying end, from the marked graph at any number of alignments; its two typed arrays travel in the worker's transfer list. The read check takes it as ReadPlacement.positions and never computes positions from listed alignments (the CSO's ruling 6.2). See Engines.

How the fixtures were generated

reference.py (MIT, because it calls a GPL-2 package) runs in a virtual environment from requirements.txt (Biopython 1.88, primer3-py 2.3.1). Steps: fetch-inputs (GenBank via E-utilities, UniProt via REST, saved with hashes in inputs/sources.json), fetch-protparam (one POST per case to the ProtParam form, responses saved in protparam/), run-orffinder (NCBI standalone binary 0.4.3, not committed; 36 runs saved in orffinder/), then fixtures, offline. The script refuses other Biopython or primer3-py versions and asserts that Biopython's DNA_NN3 equals SantaLucia 1998 Table 2, that its tables 1, 2 and 11 equal gc.prt, that each Owczarzy 2008 case took the regime its R selects, and that rerunning Mg²⁺ cases with the sodium equivalent gives identical ntthal results. peptides.py, exact_matches.py, revision-transforms.py and align_reference.py each write their own fixture and manifest entry; RDKit is not in requirements.txt and is checked only by peptides.py's version guard. Running scripts and fetching is not part of npm test.

build-codes.mts regenerates src/lib/seq/codes-data.ts from ncbi/gc.prt; codes.test.ts fails if the committed module differs.

Published copies

publish.py <version> copies the manifest and every listed file byte for byte to public/verification/sequence-toolkit/<version>/, and each set in its SETS (the read check's readcheck/) from its own directory with its own manifest to the same subdirectory, write-once against main; the read check's set is first published in 0.9.0, with SCI-09; until then sequence.test.ts checks the pipeline's copy against its manifest. Versions 0.1.0 to 0.8.0 exist; 0.2.0 is a byte copy of 0.1.0 (audit M1) and stays because published files are never replaced. 0.7.0 (17 October 2026) carries the site-directed mutagenesis primers' set (sdm-primers-v1.json); 0.8.0 (20 October 2026) adds dna-align-v1.json, the pairwise DNA aligner's fixtures (see Pairwise DNA alignment), its bytes unchanged from the set first published on the aligner's branch as 0.7.0, renumbered because main took 0.7.0 first. sequence.test.ts checks that the directory for the registry's current version matches the pipeline manifest hash for hash.

Unit tests

Run with npm test (Node's test runner over src/**/*.test.ts, content/**, pipeline/**, scripts/**). Running node --test on src/lib/seq/*.test.ts, src/lib/seq/features/*.test.ts, src/apps/sequence-toolkit/*.test.ts, src/components/tools/sequence-toolkit/*.test.ts and pipeline/sequence-toolkit/*.test.ts on 10 October 2026 gave 606 passing tests and no failures; that set includes Plasmid's modules in src/lib/seq/. Beyond the fixture tests above, notable unit tests cover the b: codec round trip (pack.test.ts), refusals and condition bounds (tm.test.ts, tm-limits.test.ts), PCR origin rotations, repeated regions and bounds (pcr.test.ts, pcr-review.test.ts), product record tampering (pcr-product.test.ts), primer design with hand-placed footprints and an origin rotation (primer-design.test.ts, 5 tests), the address schema, inbound rules, chips-to-sources and CSV rows (sequence.test.ts), UniProt import and the late-lookup guard, saved-work persistence (depth-persistence.test.ts), and NNDB transcription (structure-params.test.ts). The 3,000 random PCR cases cited in the 10 October audit were an audit-time check and are not in the repository's tests.

Browser tests

e2e/sequence-toolkit/ (not run for this page): workspace.spec.ts (detection, link state, H7 from Protein, H12 from Cell, accession-only pastes, H9 to Calc and H10 to Molecule, keeping a sequence and its object tab, FASTA export, the worker path at 120 kb and refusal over 1 Mb), tasks.spec.ts (task pages answer without the shell, per-island chunks, GenBank on islands), lookup.spec.ts (UniProt lookup guards), navigation.spec.ts (group tabs), selection.spec.ts, pcr-product.spec.ts (ambiguity, tails, origin crossing, Plasmid hand-off), design-revision.spec.ts (design, primer sets, revision review). e2e/perf/two-second.spec.ts gates the translate page's answer within 2,000 ms at 4× CPU on slow 4G with the cache primed. Size budgets are in scripts/size-budgets/sequence-toolkit.json: islands 40 KB gzip, protein MW/pI widened to 42, primer check 48 interim (release 40, measured 42.9 KB on 10 October 2026), definition chunk 30 interim (release 25). See Build, test and CI.

Licence constraints in the code

  • Primer3 (GPL-2.0-or-later): primer3-py and ntthal are offline oracles only. No Primer3 code or primer3_config parameter file is copied or ported; every structure parameter is transcribed from the cited papers or NNDB. Primer design is a separate implementation and the depth spec repeats that Primer3 must not be shipped or ported.
  • The register of items held for the General Counsel (docs/board/open-questions.md) records that the account of ntthal's per-hairpin salt count in structure.ts drew on the builder's memory of thal.c from training, not on reading it, with no code or tables copied. Whether that matters is held for the General Counsel.
  • SMS2 (GPL-3) and EMBOSS (GPL applications) are behaviour references only. CRISPOR is not run (academic-only licence). Biopython is an offline oracle; its tables are not copied.
  • Source licences in sourceMeta: NCBI genetic codes NCBI-public-domain; UniProt CC-BY-4.0 (read 2026-09-27); ProtParam cited-data, its terms "To be checked"; the papers cited-data or cited-method; NNDB cited-data with licence line "To be checked" (D-0014). RDKit (BSD-3-Clause) is an oracle only.
  • content/apps/sequence-toolkit.ts and content/tools/sequence-toolkit.ts name GPL tools in excludedSources and oracles; scripts/licences.mjs exempts these two files for that reason.
  • The oracle scripts carry an MIT header naming "The Drybench authors" (Q-20 open). Licence facts are collected in Dependencies and licences and Data sources.

Known gaps, defects and divergences

Divergences between public copy, design documents and code:

  • Closed on 11 October 2026 (Wave 6 item 1): the FAQ answers "Can I design primers here?" with the bounded design and its limits; the brief's release limit "no primer design" (docs/design/brief-sequence-toolkit.md) is now historical.
  • Primer design's published fixture and the offline Primer3 comparison landed on 11 October 2026 (primer-design.json, docs/science/evidence/primer-design-2026-10-11/); its MethodRef, METHODS and verificationSets entries and its public operation page follow in the same wave (Wave 6 item 1).
  • Closed on 12 October 2026: the limit line (content/tools/sequence-toolkit.ts limits[8]) said binding-site scans of inputs over 100 kb ran in a Web Worker; it now says what the code does: six-frame, ORF and motif scans move to the worker from 100 kb, and the binding-site diagnostics are skipped for templates of 100,000 bases or more (the worker's binding request is never sent).
  • The primer-check task page's worked example still computes a product length from the first near-match site pair with productBetween (src/apps/sequence-toolkit/tasks.marketing.tsx:222), which docs/design/pcr-product-architecture.md says was removed.
  • The construct object tab is titled "before and after cleavage" but shows only uncleaved numbers; the brief's ORF count and default-condition Tm on the sequence tab are not implemented (src/apps/sequence-toolkit/object-tab.tsx:77-104).
  • Buffer presets described in the brief (§3.1, §4.1) are not implemented; only typed conditions exist, and K⁺ and Tris cannot be entered although the engine supports them.
  • The brief's b: codec included a circular flag; the implementation deliberately has none (src/lib/seq/pack.ts header).
  • Closed on 12 October 2026: pipeline/sequence-toolkit/README.md now gives the enforced gate (0.4 kcal/mol for hairpins, 0.1 for dimers, on rescored ntthal structures). It still does not describe the exact-match, revision, peptide or selection fixtures.
  • METHODS_BY_OP["primer-check"] omits salt1998, so the export's method block does not list the SantaLucia 1998 correction when the person selects it in primer check, although the rows name it (src/apps/sequence-toolkit/rows.ts:36).
  • Components hold user-visible copy contrary to the copy rule: pcr-product-panel.tsx (headings, buttons, messages) and primer-panel.tsx (the binding diagnostics heading and notes, the forward-primer refusal).
  • pasteCopy.translationCheck (comparison with a GenBank /translation) has no caller.

Scientific and behavioural limits:

  • No special triloop/tetraloop bonuses; no multibranch loops; ntthal's chosen structure differs from this engine's on 7 of 13 hairpin fixtures with a structure (the engine reports lowest ΔG, ntthal highest Tm).
  • Tm and structures use different salt models (Owczarzy for Tm, a sodium equivalent for ΔG).
  • The Owczarzy 2008 free Mg²⁺ range and the SantaLucia 1998 entropy range have not been read against the papers (Q-24, before Sequence leaves preview); no oracle case at 10 mM Na⁺ yet.
  • ORFs and binding diagnostics treat sequences as linear; circular-origin ORFs are not found.
  • The Bjellqvist N-terminal values were recorded "from memory" in the brief; agreement is empirical on 22 ProtParam cases. Only one pK set is offered.
  • Tag and protease-site detection is by motif; a motif inside a protein is reported as a site.
  • Template length messages differ: readPcrTemplate refuses text over 100,000 characters with "at most 50,000 bases"; 50,001 to 100,000 bases reach the engine and are refused as template-limit.
  • Fixed 12 October 2026 (SCI-09): the design search and the primer-set reader take the PCR engine's tail limit, PCR_LIMITS.tail (200), from the engine-free src/lib/seq/pcr-limits.ts. The tail fields of the primer-check and primer-design panels take TAIL_FIELD_GUARD (400, from the same module, pinned equal to the address's TAIL_LINK_GUARD by sequence.test.ts) since the batch 1 review of 14 October 2026: at 200 a paste was cut short without a word, spaces counting toward the field and not the engine, and a longer tail was cut to 200 and accepted rather than refused. The address keeps its 400-character link guard for o2, ftail and rtail, so a longer tail in a link reaches the engine, which refuses it by name; the engine refuses annealing parts over 200.
  • The local UniProt index (about 3.1 MB) loads on every workbench visit (10 October audit, section 4).

Items the 10 October audit lists as open for Sequence and Plasmid that this page did not re-verify individually: whether the Tm and structure engines still disagree on the admissibility of one buffer (Na⁺ 0, Mg²⁺ 1, dNTP 2 mM), and the lookup copy not saying the index holds reviewed human entries only. docs/NEXT.md lists for Sequence: short-loop bonuses, unchecked structural-loop classes, oligo ε260, H9's oligo-to-Calc scope, accession fetching and task-page parser loading.

Extending safely

  • Add an operation by adding its id to OPERATION_IDS (content/apps/sequence-toolkit/ids.ts), its copy module, a model-<id>.ts adapter, a panel, an island and task page, its keys in OPERATION_KEYS and sequenceSchema, its rows and METHODS_BY_OP entry, and a registry task. Keep islands importing model-core.ts and their own model only; check the size budget.
  • Put new scientific code in src/lib/seq/ as pure TypeScript with relative imports, define its MethodRef in a methods-*.ts module, join it in content/apps/sequence-toolkit/methods/, and give its constants table a registry source in provenance.ts SOURCE_OF.
  • Before marking a method "verified", write an independent oracle script under pipeline/sequence-toolkit/ that does not read src/, record it in manifest.json, add a verificationSets entry and publish a new version directory with publish.py. Never edit a fixture by hand: fixtures.test.ts fails on any hash change.
  • Changing an engine's numbers changes every versioned example and every saved result's meaning. Bump the method version, and consider whether SEQ_VERSION and the examples' pins must change; there is no mechanism to keep an old version running.
  • Do not port or copy Primer3, SMS2, EMBOSS or Biopython code or tables. Transcribe parameters from the papers with the reading date, and say so when a GPL tool is proposed.
  • New address keys need a check that refuses rather than clamps, and persisted payload changes need a reader that accepts old versions (readSequenceWorkspace accepts v1 and v2) and a schema version bump.
  • Any new host must be declared in the registry dataPolicy.endpoints so the CSP includes it; workers must never fetch.
  • Run npm run lint, npm test, npm run build, npx tsc --noEmit and npm run test:e2e before finishing; see Build, test and CI.

Registry and content, Storage and state, Security and network, Plasmid, Protein, Calc, Molecule, Engines, Oracles and verification, Data pipeline, Dependencies and licences, Known issues.

Wave 6 primer-design loading boundary (11 October 2026)

The public search uses primer-design-panel.tsx; workbench-only collection controls and witness labels live in primer-design-workbench.tsx and its on-demand collection. primer-set-from-design.ts converts a design receipt without importing the historical reader, PCR engine or CSV exporter. panel-context.tsx owns the single method-chip context, re-exported by parts.tsx. The public island reads this context directly, without pulling in the unrelated panel controls. Thermodynamic controls load only when the constraints disclosure opens (design-conditions.tsx); failure has a visible retry button, and closing it retains the conditions in address state. Search always reads the same conditions and defaults independently of those controls. The island ceiling stays 40 KB; the workbench wrapper has a 40 KB ceiling and the disclosure controls a 20 KB ceiling. The receipt downloader keeps its object URL alive for 60 seconds so downloads are not cancelled by early revocation.

The walk binding (the primer-pair@1 walk's raw-control checkpoint through useRecordDraft and its four sequence.design-* walk actions) is a separate component, primer-design-walk.tsx, that renders nothing; the workbench wrapper passes it to the panel as walk, and the island passes nothing, so the walk run store, the object runtime and the draft writer stay out of the island's chunk. Measured on 12 October 2026 after npm run build (size-check --baseline docs/progress/assay-stage-size-base.json): the island 36.5 of 40 KB (47.6 on main before, REL-22), the workbench wrapper 7.0 of 40 KB, the conditions chunk 7.2 of 20 KB; the extinction island 41.7 of 42 KB.

Sequence to Plasmid (Wave 6 item 3, prepared pending checks)

The send action carries the complete editable DNA record, retained primer sets and optional PCR receipt in an existing construct object. The cross-link declares only name; mine remains fragment-only. Plasmid admits the payload in its own worker and names actual placed annotations and omitted pairs on arrival. Sender flush, runtime load and record creation are guarded against draft changes, account epoch changes and unmount. Historical v1 without a collection remains supported; unknown versions refuse.

The 0.6 qualification fixtures precede the reader change. Only requests receive default expansion and effective salt selection. Retained top-level and nested correction labels and every normalized ionic/concentration field must agree literally; normalizing retained claims must never erase a contradiction. Historical v1 missing temperatures remain missing, and accepted retained text is not recomputed or modernized. This implementation is prepared pending checks.

The extinction task uses the shared protein-property and cleavage calculation without loading or running peptide structure generation. The protein task adds peptide generation to the same values. This keeps the extinction island inside its existing budget without changing scientific calculations.

Primer pair walk (prepared, not yet validated)

primer-pair@1 loads its definition and generated value projection only on entering the walk. The independent published primer-walk-v1.json pins the template, constraints, selected pair identity, full product and mapped features. Generic panel actions call the same design worker and exact PCR product path as the controls. The selected pair is found by identity rather than its displayed row index. The PCR carry ref is recorded as run-owned before the following step navigates to Plasmid, enabling idempotent Back/Next and whole-run removal.

Only raw controls are checkpointed in a bounded, independently versioned optional walkDrafts block on the original run-owned template construct. Results are recomputed after hydration, by run(inputs) with the restored draft's inputs from useRecordDraft's onHydrated callback (no effect; MNT-15); they are not stored as checkpoints. Atomic checkpoint writes validate run, app, exact ref, schema, scope epoch and current draft generation, preserving all other data. The implementation and browser specs are prepared source pending validation; the scientific search remains capped and template-local.

Independent sequence batches (16 October 2026)

The workspace's batch disclosure freezes an explicit input owner, then passes live sequence text and operation settings to the independent batch coordinator. Each FASTA/GenBank record runs separately; edits mark previous rows stale. Recovery and recording preserve per-record failures and exact inputs. See Independent analysis batches for bounds, supported operations, cancellation and idempotent child recording.

Public reference workflows (16 October 2026)

Unchanged UniProt source and validation strings now live in content/apps/sequence-toolkit/source-copy.ts. The lookup interface reuses them; local operation islands import that small module without the remote-search interface's copy. This preserves the primer-design bundle boundary when the public reference shell is present in the build.

Two public reference workflows seed the native ORF/translation and primer thermodynamics views. Their copy records model assumptions and does not introduce a separate analysis engine. See Public reference workflows for the shared route, state, source and export contracts.

In the workbench (views/workbench.tsx, outside the standalone operation islands), sequenceRefusalGuidance (content/apps/sequence-toolkit/refusal-guidance.ts, [data-sequence-refusal-guidance="<codes>"]; CSO memo of 20 October 2026, §7; CPO memo of 20 October 2026, §6) renders only from Oligo Tm's own result: a TmRefusal shorter than TM_MIN_LENGTH_NT (2 nt, tm.ts; the kind is read from the engine's own reason, tmLengthReason), or a computed Tm's outsideFit notes (owczarzy-2004-monovalent, owczarzy-2008-free-mg), each stated with its value and range from the note. Non-ACGT, condition and numerical-domain refusals keep their own reason with no added statement. It never draws on an empty workbench, an optional template left empty or another operation; Primer check, Primer design and Site-directed mutagenesis show their refusals in their own panels. The link to /pcr/app is labelled with the tool's title from content/education-tools.ts. Tests: src/lib/methods/refusal-guidance.test.ts.