Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

146 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nucle-OS — DNA Storage Engine

Release CI License: MIT

A complete software-defined DNA storage operating system.

The same way software-defined networking abstracts physical switches, Nucle-OS abstracts physical DNA synthesizers. It is the driver layer that molecular data storage plugs into.


15 lines, one command

This is the whole pitch: a pool schema with real biological constraints, a noise-aware probabilistic recovery type, and a pipeline that encodes, protects, stores, and cryptographically verifies a real file — end to end, against the actual engine, not a mock.

pool medical_archive: DnaPool {
    codec: YinYang,
    redundancy: 4x,
    profile: Illumina
}

let noisy: Pool<Illumina, 0.35%> = simulate medical_archive under Illumina
let recovered: Pool<Recovered> = consensus_vote(noisy, coverage: 10x)

pipeline archive_patient_records {
    encode "patient_records_2026.csv" using YinYang,
    protect with redundancy 4x,
    store into medical_archive,
    verify roundtrip
}
$ nucle run docs/examples/hero.nsl
✓ store into medical_archive: Stored 'patient_records_2026.csv' (109 bytes → 4 data + 4 parity = 8 strands, 2.00× redundancy, primer=P0000)
✓ verify roundtrip: 'patient_records_2026.csv' recovered exactly

╔══════════════════════════════════════╗
║         NucleOS Pool Status          ║
╠══════════════════════════════════════╣
║ Files:               1               ║
║ Total strands:       8               ║
║ Data strands:        4               ║
║ Parity strands:      4               ║
║ Nucleotides:      3204               ║
║ Avg strand len:    400 nt            ║
║ Redundancy:      2.00×              ║
╟──────────────────────────────────────╢
║ Files:                               ║
║   patient_records_2026.csv (ID: archive-7098, 109 B, 4d+4p strands, 2.0×)
╚══════════════════════════════════════╝

--- Recovery Manifest: patient_records_2026.csv ---
Observed Error Rate: 0.0000%
Consensus Method:    majority-vote
Sequencing Profile:  pristine
Recovered Strands:   4
ECC Success:         true
Positions w/ errors: 0 of 4

The pool declaration is chemistry-checked at compile time (GC balance, homopolymer limits) before a single strand is generated. The probabilistic Pool<Illumina, 0.35%> type tracks the sequencer's real error rate through consensus_vote — the type system, not a comment, is the proof that noise was accounted for. And verify roundtrip isn't cosmetic: nucle run reads the original file back out through the full encode → protect → store → decode path and byte-compares it, so ✓ verify roundtrip: recovered exactly above is a real assertion that passed, not a printed string. Try it yourself:

nucle run docs/examples/hero.nsl

Architecture

┌─────────────────────────────────────────┐
│           Agent Interface Layer          │  ← AI agent for metadata-similarity file ops
├─────────────────────────────────────────┤
│         Hardware Bridge / Provider       │  ← typed requests → mock/file-export/vendor
├─────────────────────────────────────────┤
│              VFS / File API              │  ← read(), write(), query() abstractions
├─────────────────────────────────────────┤
│          Retrieval & Index Layer         │  ← vector index, CRISPR-sim random access
├─────────────────────────────────────────┤
│         Error Correction Layer           │  ← codec, noise model, repair pipeline
├─────────────────────────────────────────┤
│          Encoding / Decoding Layer       │  ← binary ↔ ATCG with constraints
├─────────────────────────────────────────┤
│           Synthesis Simulator            │  ← inject realistic DNA errors
└─────────────────────────────────────────┘

Each layer is a real engineering problem. This project owns the whole stack.


Layers

Layer 1 — Synthesis Simulator (nucle_synth)

Models the exact error distributions of real DNA synthesizers — substitution rates, insertion/deletion frequencies, strand dropout. This is the "noisy channel" everything above must survive. Parameterised to mimic different hardware profiles (Illumina, Oxford Nanopore, Twist Bioscience).

Layer 2 — Encoding Engine (nucle_codec)

Converts arbitrary binary files into valid DNA sequences with hard biological constraints enforced:

  • GC content balance (40–60%)
  • No homopolymer runs longer than 3 bases
  • No secondary structure formation (hairpins/palindromes)

Implements multiple codec strategies:

  • Ternary Rotating Cipher (Goldman et al.) — ~1.58 bits/nt, zero homopolymers by construction
  • DNA Fountain (Erlich & Zielinski) — ~1.57 bits/nt, rateless, near-optimal density

Layer 3 — Error Correction (nucle_ecc)

DNA is a noisy channel with insertion/deletion-heavy error profiles — unlike disk or network. This layer provides:

  • Reed-Solomon outer code — strand-level erasure recovery
  • Fountain/LT erasure codes — rateless recovery from arbitrary strand loss
  • Consensus sequencing — majority voting across multiple strand copies
  • Full repair pipeline — orchestrated multi-stage error correction

Layer 4 — Retrieval & Index (nucle_index)

The hardest unsolved software problem in the field. When millions of DNA strands exist in a pool, how do you retrieve one file without reading everything?

  • Primer-based addressing — unique address primers per file
  • CRISPR random access simulation — selective strand amplification
  • Vector similarity index — content-addressable lookup
  • Metadata similarity search — ranks files by structural resemblance (filename, size, type, content hash), not full-text meaning — an honest description of hand-engineered feature vectors, not a learned embedding model

Layer 5 — VFS / File API (nucle_vfs)

Abstracts all layers behind clean syscall-style interfaces:

  • dna_write(name, data, redundancy) — encode → ECC → tag → store
  • dna_read(query) — search → retrieve → decode → return
  • dna_stat(pool) — pool statistics, health metrics
  • dna_delete(name) — mark strands for removal

DNA storage needs a proper ABI. This layer provides it. NucleOS::open(pool_dir, max_files)/persist(pool_dir) make a pool durable across separate CLI invocations (not just one process's own memory) — see Pool storage location below. NucleOS::open_encrypted/enable_encryption/disable_encryption add optional, passphrase-protected encryption at rest for that same durable state — see nucle encrypt-pool/decrypt-pool above.

Rewrite/versioning. dna_writeing a filename that already exists no longer rejects it as a duplicate — it stores the new content as the next version (WriteResult::version) and archives the previous version rather than deleting it (old strands, old primer — nothing physically removed). dna_read/dna_list/dna_stat always resolve to the current version, so nothing else changes shape; dna_history(filename) lists every version oldest-first, and dna_read_version(filename, version) reads a specific one, current or historical. dna_delete removes the entire version chain (DeleteResult::versions_removed), not just the current version — there's no "delete just the latest, keep history with no current version to point back to" concept. See docs/architecture.md for the full design and what changed in the shipped .nsl examples as a result.

Layer 6 — Agent Interface (nucle_agent)

A ReAct agent that takes natural-language file operations, plans across the VFS layer, and executes them. "Store last year's medical archive with 3x redundancy" becomes a full pipeline down to the encoding layer. Also understands migrate ("migrate readme.txt to codec yin-yang with 4x redundancy") and help (lists every tool and its parameters) — rule-based throughout, no LLM dependency.

Layer 7 — Hardware Bridge (nucle_hardware)

The execution boundary between compiled NucleScript plans and real lab hardware. nucle_lang::hardware only ever collects typed HardwareRequests (Synthesis, Sequencing, Destructive) from an effect-checked program; nucle_hardware::Provider is the one trait that actually submits them via submit(&self, batch) -> Box<dyn JobHandle> (returns immediately; poll status() or block on wait()) — via MockProvider (instant dry run), DelayedMockProvider (dry run simulating real hardware latency on a std::thread), FileExportProvider (writes a JSON batch for lab submission), or four real vendor adapters: TwistProvider/IdtProvider/IlluminaProvider (genuine HTTP clients against each vendor's actual public API) and NanoporeProvider (a genuine gRPC client compiled against Oxford Nanopore's real, vendored MinKNOW .proto files, since ONT's public API controls a local instrument rather than exposing a cloud REST endpoint like the other three). None has live vendor credentials to run against in this project's environment, and none is wired into the CLI's --provider flag yet — see docs/architecture.md for exactly what's confirmed real per vendor versus left for the integrator to supply.

Layer 8 — Block-Device Abstraction (nucle_blockdev)

A fixed-size, address-addressed block device sitting parallel to (not underneath) nucle_vfs's whole-file syscall API — the seam a real filesystem driver actually needs (read_block(lba)/write_block(lba, data)), so the OS layer is ready when real DNA drives ship. Two backends model two real, structurally different DNA storage approaches: SynthesisArrayBlockDevice (Atlas/imec — a dense electrochemical synthesis array on a CMOS ASIC; write-once, real array parallelism across blocks) and RewritableNanoporeBlockDevice (University of Missouri/Mizzou — synthesis-free frameshift encoding + nanopore duplex-interruption decoding; genuinely rewritable in place, but every read/write goes through one serial sensor). Both compose the same real nucle_codec encode/decode and nucle_ecc consensus + Reed-Solomon layers nucle_vfs already uses at the file level, and nucle_blockdev::blob demonstrates a minimal filesystem-like consumer built generically against the trait. See docs/architecture.md for exactly what's confirmed real per approach (the write-once/rewritable distinction, which noise profile approximates each) versus illustrative-but-unconfirmed (the specific latency and parallelism figures — neither public source states one).


Building

# Build the entire workspace
cargo build --workspace

# Run all tests (300+ tests)
cargo test --workspace

# Run the CLI
cargo run --bin nucle-cli -- --help

Demo — It Actually Works

Codec Benchmark

$ nucle bench

╔══════════════════════════════════════════════════════════════════╗
║               DNA Codec Benchmark Comparison                    ║
╠══════════════════════════════════════════════════════════════════╣
║ Codec                │  bits/nt │   GC % │ Hpol │ Bio │  R/T ║
╟──────────────────────┼──────────┼────────┼──────┼─────┼──────╢
║ ternary-rotating-cipher │    1.099 │  48.6% │    1 │  ✗  │  ✓   ║
║ ternary-rotating-cipher │    0.628 │  48.9% │    2 │  ✗  │  ✓   ║
║ yin-yang             │    1.798 │  37.8% │    4 │  ✗  │  ✓   ║
║ dna-fountain         │    0.824 │  26.0% │   29 │  ✗  │  ✓   ║
╚══════════════════════════════════════════════════════════════════╝
  Best density:    yin-yang (1.798 bits/nt)
  Fastest encode:  yin-yang (55 μs)

  Bio = all strands pass biological constraints (GC 40–60%, homopolymer ≤ 3)
  R/T = encode → decode roundtrip produces identical data

Yin-Yang leads in density at 1.798 bits/nt — nearly 2× the ternary codec. The Yang rule maps each bit to an AT/GC partition, guaranteeing ~50% GC on balanced data. The Yin rule uses the previous nucleotide as context to reduce homopolymer formation. See docs/references.md for the full algorithm (Ping et al. 2022).

The two ternary-rotating-cipher rows are the same codec run in its two configurations (no overlap, and default overlap) — same name, different density/GC/homopolymer profile.

Why does every codec show for Bio here? The overall GC%/homopolymer columns above look fine, but Bio also checks two things those columns don't show: local GC content in a sliding window, and palindromic runs long enough to form a hairpin. On this small 89-byte benchmark input, at least one strand from every codec trips one of those two additional checks. On production-size files (≥1 KB) this is far less likely, since a single bad local window has much less influence on the whole strand set — see docs/examples/fixtures/-backed results below for real files that do pass.

Full-Pipeline Benchmark

nucle bench benchmarks codecs in isolation; nucle benchmark runs the real write → simulate-noise → read pipeline against the standard fixtures in docs/examples/fixtures/, reporting GC distribution, homopolymer violations, and a real Monte-Carlo recovery probability and cost estimate — not placeholders:

$ nucle benchmark --profile pristine -r 4

╔══════════════════════════════════════════════════════════════════════════════════════════════════╗
║                              NucleOS Full-Pipeline Benchmark                                      ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════╣
║ File               │ Size(B) │ Strands │ Error Rate │ Recover │ Cost(USD) │    GC% │  HpolV ║
╟────────────────────┼─────────┼─────────┼────────────┼─────────┼───────────┼────────┼────────╢
║ small_text.txt     │      96 │       8 │      0.00% │    PASS │ $  0.0065 │  47.5% │      0 ║
║ archive.bin        │     327 │      18 │      0.00% │    PASS │ $  0.0227 │  46.2% │      0 ║
║ sample.fasta       │     176 │      12 │      0.00% │    PASS │ $  0.0130 │  45.3% │      0 ║
║ image.png          │     294 │      16 │      0.00% │    PASS │ $  0.0194 │  47.0% │      0 ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════╝

Under a noisy channel like Illumina, this used to fail recovery: the ternary decoder is strict and rejects substitution-corrupted strands rather than soft-decoding them, and Reed-Solomon alone only recovers a strand that's entirely missing, never one that survived corrupted. The fix is consensus voting across coverage copies — sequencing each strand multiple times and majority-voting corrects substitution errors regardless of which copy has them — and it's now wired into the real dna_read path (nucle_ecc::consensusnucle_vfs::syscall::dna_read), not just implemented in isolation:

$ nucle benchmark -p illumina -r 4

║ small_text.txt     │      96 │       8 │      0.32% │    PASS │ $  0.0648 │  47.5% │      0 ║
║ archive.bin        │     327 │      18 │      0.32% │    PASS │ $  0.2268 │  46.2% │      0 ║
║ sample.fasta       │     176 │      12 │      0.30% │    PASS │ $  0.1296 │  45.3% │      0 ║
║ image.png          │     294 │      16 │      0.31% │    PASS │ $  0.1944 │  47.0% │      0 ║

This fixes Illumina. Nanopore is still broken, and we chased why three times. Fix one: consensus voting now aligns each read to the group's reference before voting instead of comparing raw positions, so it tolerates indels, not just substitutions. Fix two, bigger: primer matching (nucle_index::primer::PrimerPair) required an exact-position match, so a single indel inside a primer — routine at Nanopore's error rate — made retrieval drop the whole strand before it ever reached consensus, the real dominant blocker. Fix three: pairwise realignment against one arbitrarily-picked noisy reference read has a hard ceiling once a read carries several simultaneous indels at once (the real Nanopore regime), so nucle_ecc::consensus is now genuine partial-order alignment (POA) — every read folds into one shared graph with edge-weighted voting, so a majority correctly outvotes a minority stray insertion at any position, including the very first or last base (previously it couldn't). Consensus now also polishes over multiple rounds (reseed from the previous round's own result, re-fold every read, repeat to a fixed point — what Racon/Medaka do), verified not to regress Illumina this time after an earlier attempt's double-counted vote weight briefly did. A synthetic worst-case test still landed 1 base off out of 43 even after polishing converged, and the first diagnosis for that ("column identity fragmenting") turned out to be wrong once tested further — the real cause is that sequential graph construction is fold-order dependent (folding the exact same reads in reverse order gave the exactly correct answer, no other change), and polishing can't fix that since every round reuses the same fold order. build_consensus now re-runs the pipeline with a second and, if needed, third fold order and takes whichever result a majority agree on, which resolves that test exactly — gated on the first pass's own confidence so realistic (non-adversarial) cases don't pay the extra cost.

Fix four, and this one wasn't in the consensus algorithm at all: the ternary codec's own padding used a constant trit, and its 4-byte length header has leading zero bytes for any file under 16MB — a constant trit run degenerates, through the rotating cipher, into a literal TATATATATATATATAT... repeat dozens of bases long at the start of essentially every encoded file. That self-inflicted tandem repeat, not the noise or the aligner, was the actual cause of several residual errors that looked like a fundamental POA limit — tandem repeats are famously hard to align under indel noise for reasons that have nothing to do with how good the aligner is. Fixed by whitening every strand's trits with a deterministic, position-addressable pseudo-random stream before the cipher sees them, reversed per-strand at decode (TernaryCodec::whiten_segment). Verified: the pathological repeats are completely gone from the encoded output, and residual consensus errors under real Nanopore noise are now small, localized 1-2-base insertions, not sprawling corruption.

All four fixes are covered by dedicated regression tests, including a crash found by fuzzing realistic-rate Nanopore noise at 50x coverage.

Fix five: Reed-Solomon itself turned out to have two real bugs, both silent. First, parity symbols are arbitrary GF(256) values (0-255), but they were being packed into DNA one base per byte via the same 2-bit Nucleotide::from_bits used for already-restricted data values — any parity byte above 3 (the overwhelming majority of them) was silently dropped, destroying nearly every parity strand ever written. Second, a parity strand that failed to arrive was dropped from its array via filter_map instead of leaving a gap, which shifted every later parity strand onto the wrong evaluation point and corrupted the whole stripe's math. Fixed by packing each parity byte into 4 bases (DnaStrand::from_packed_bytes/unpack_bytes) and keeping erasures as Option-per-slot everywhere so a missing strand's true codeword position is never lost. On top of that, Reed-Solomon itself was erasure-only (could rebuild a strand marked missing, but could never correct one that survived consensus wrong-but-present); it's now a real combined error-and-erasure decoder (Berlekamp-Welch), so a strand that comes back from consensus with a residual wrong base gets corrected automatically, without knowing in advance which strand that was. Verified directly: dedicated unit tests confirm blind single-strand correction and correct decode across a parity gap in the middle of the list; the full workspace suite (all crates, all doctests, the 50x-coverage Nanopore fuzz test) passes with zero regressions.

nucle benchmark -p nanopore -r 4/-r 12 used to fail at realistic settings, and ablation testing (comparing -r 0 through -r 50 on the same noisy data) showed the exact same failure at every redundancy level — but that "~14% combined error rate" was itself a bug, not Nanopore's real rate: the benchmark was pairing Nanopore with itself for both synthesis and sequencing, silently doubling its error rate instead of pairing it with realistic Twist synthesis (SimulationConfig::twist_nanopore(), which already existed and just wasn't being used). Fixed (nucle_cli::realistic_synth_seq_pair): nucle benchmark -p nanopore -r 12 now reports ~7% error rate per file (the actually-documented rate) and passes every standard fixture, and consensus + Reed-Solomon reliably recover at the exact 50x-coverage scale this limitation was originally diagnosed at (nucle_vfs::tests::test_nanopore_recovery_is_reliable_at_the_original_diagnostic_scale_thorough). See docs/architecture.md for the full investigation.

$ nucle benchmark -p nanopore -r 12

╔══════════════════════════════════════════════════════════════════════════════════════════════════╗
║                              NucleOS Full-Pipeline Benchmark                                      ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════╣
║ File               │ Size(B) │ Strands │ Error Rate │ Recover │ Cost(USD) │    GC% │  HpolV ║
╟────────────────────┼─────────┼─────────┼────────────┼─────────┼───────────┼────────┼────────╢
║ small_text.txt     │      96 │      16 │      7.16% │    PASS │ $  0.0324 │  47.5% │      0 ║
║ archive.bin        │     327 │      26 │      7.04% │    PASS │ $  0.1134 │  46.2% │      0 ║
║ sample.fasta       │     176 │      20 │      7.14% │    PASS │ $  0.0648 │  45.3% │      0 ║
║ image.png          │     294 │      24 │      7.00% │    PASS │ $  0.0972 │  47.0% │      0 ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════╝

At the CLI's default -r 4 (fine for Illumina's ~0.3% rate, thin for Nanopore's ~7%), 3 of the 4 standard fixtures still pass; sample.fasta still FAILs — expected, not a regression, since 4 parity strands is genuinely low redundancy at Nanopore's per-base rate, and raising -r is exactly the intended knob for that tradeoff, not a bug to fix.

End-to-End Roundtrip: Encode → Noise → Recover

$ nucle encode README.md -o readme.dna
✓ Encoded README.md → readme.dna (2277 strands)

$ nucle simulate README.md -p illumina
╔══════════════════════════════════════╗
║     Synthesis Simulation Results     ║
╠══════════════════════════════════════╣
║ Profile:                    illumina ║
║ Coverage:                          1×║
║ Input:                  2277 strands ║
║ Output:                 2277 strands ║
║ Error rate:                  0.37%   ║
║ Surviving:                   96.3%   ║
╚══════════════════════════════════════╝

$ nucle decode readme.dna -o recovered.txt -s 56915
✓ Decoded readme.dna → recovered.txt (56915 bytes)

56,915 bytes → 2,277 DNA strands × 162 nt avg = 368,874 nucleotides. Illumina noise: 0.37% error rate, 3.7% strand loss — 100% data recovery.

Realistic Sequencing: 10× Coverage with Consensus

Real sequencing runs at 10–50× coverage — you sequence the pool many times and consensus-vote across copies. This is the realistic scenario:

$ nucle simulate README.md -p illumina -c 10
╔══════════════════════════════════════╗
║     Synthesis Simulation Results     ║
╠══════════════════════════════════════╣
║ Profile:                    illumina ║
║ Coverage:                         10×║
║ Input:                  2277 strands ║
║ Output:                22770 strands ║
║ Error rate:                  0.36%   ║
║ Surviving:                   96.1%   ║
╚══════════════════════════════════════╝

10 independent noisy copies per strand. Consensus voting across copies eliminates per-base errors; ECC handles the ~4% strand dropout. This is how real DNA storage systems achieve reliable recovery.

Full Stack: Store with ECC + CRISPR

$ nucle store README.md -r 4
✓ Stored 'README.md' (56915 bytes → 2277 data + 40 parity = 2317 strands,
  1.02× redundancy, primer=P0000)

╔══════════════════════════════════════╗
║         NucleOS Pool Status          ║
╠══════════════════════════════════════╣
║ Files:               1               ║
║ Total strands:    2317               ║
║ Data strands:     2277               ║
║ Parity strands:     40               ║
║ Nucleotides:    487474               ║
║ Avg strand len:    210 nt            ║
║ Redundancy:      1.02×              ║
╟──────────────────────────────────────╢
║ Files:                               ║
║   README.md (56915 B, 2277d+40p strands)║
╚══════════════════════════════════════╝

NucleScript — Declarative DNA Operations Language

Note

Official Language & Preset Ecosystem: Visit the Nuclescript Organization — official packages live in the Packages Registry, and the interactive web playground is live in your browser or published standalone at Nuclescript/playground.

NucleScript is a domain-specific programming language for DNA storage operations. NucleScript source files use the .nsl extension. A program describes pools, storage operations, retrieval queries, simulations, and pipelines; the compiler validates syntax, pool schemas, and hardcoded DNA strand constraints before lowering operations to NucleOS VFS calls.

pool archive: DnaPool {
    codec: Ternary,
    redundancy: 3x,
    profile: Illumina
}

store "sample_a.txt" into archive {
    redundancy: 4x,
    tag: ["docs", "demo", "nuclescript"]
}

Run it with:

$ nucle run docs/examples/store.nsl
✓ store into archive: Stored 'sample_a.txt' (31 bytes → 2 data + 4 parity = 6 strands, 3.00× redundancy, primer=P0000)

╔══════════════════════════════════════╗
║         NucleOS Pool Status          ║
╠══════════════════════════════════════╣
║ Files:               1               ║
║ Total strands:       6               ║
║ Data strands:        2               ║
║ Parity strands:      4               ║
║ Nucleotides:      3156               ║
║ Avg strand len:    526 nt            ║
║ Redundancy:      3.00×              ║
╟──────────────────────────────────────╢
║ Files:                               ║
║   sample_a.txt (31 B, 2d+4p strands, 3.0×)
╚══════════════════════════════════════╝

NucleScript pipeline programs can also verify a full roundtrip:

$ nucle run docs/examples/pipeline_backup.nsl
✓ store into archive: Stored 'sample_a.txt' (31 bytes → 2 data + 4 parity = 6 strands, 3.00× redundancy, primer=P0000)
✓ verify roundtrip: 'sample_a.txt' recovered exactly

DNA-native Sequence literals are also part of the language and are validated at compile time:

seq primer_p0: Sequence = "ATCGATCGGCTAGCTA"
let primer_p1 = seq"ATCGATCG-GCTAGCTA"

NucleScript also tracks probabilistic pool types through simulation and consensus recovery. Pool<P, E> carries the hardware profile or recovery state plus an optional compiler-checked error budget:

pool archive: DnaPool {
    codec: Ternary,
    redundancy: 3x,
    profile: Illumina
}

let noisy: Pool<Illumina, 0.35%> = simulate archive under Illumina
let recovered: Pool<Recovered> = consensus_vote(noisy, coverage: 10x)

consensus_vote (and protect) are NucleScript's two built-in functions — ordinary FunctionTable entries resolved through the exact same lookup a call to your own fn goes through (arity checking, effect propagation, "did you mean X?" suggestions), not a separate hardcoded case per built-in. See docs/stdlib.md for both signatures.

Effectful biological operations are explicit in the type system. Hardware-backed synthesis and sequencing require confirm hardware; destructive operations require confirm physical_key. The compiler lowers programs through a bio-aware MIR, optimizes redundancy for the selected profile and coverage, and can emit a no-hardware simulation plan:

let strands: Pool<Twist, 0.03%> = synthesise archive via Twist confirm hardware
let reads: Pool<Illumina, 0.35%> = sequence strands via Illumina confirm hardware
delete "old_archive.bin" from archive confirm physical_key

if/for and comparison/boolean operators (==, !=, <, >, <=, >=, &&, ||, !) let a program branch on a pool's inferred error rate or repeat an operation over a list of pool names, without hand-duplicating blocks. Both are resolved entirely at compile time — the type checker evaluates the condition once and keeps only the taken branch, and unrolls a for by substitution — so the compiled plan itself never contains a branch or loop:

let noisy: Pool<Illumina, 0.35%> = simulate archive under Illumina

if noisy > 0.1 {
    let recovered: Pool<Recovered> = consensus_vote(noisy, coverage: 10x)
} else {
    let recovered: Pool<Recovered> = consensus_vote(noisy, coverage: 2x)
}

for target in [archive] {
    store "sample_a.txt" into target { redundancy: 4x }
}

Result<T, E> and ? make VFS failures genuinely catchable — the first real runtime behavior in the language. Before this, store/retrieve/ delete either succeeded or aborted the entire program; there was no way for a NucleScript program to observe, inspect, or recover from an operation failure. store/delete can now also appear in expression position, producing a Result<T, Str> a ? can unwrap or propagate to the enclosing function's own Result return type:

pool primary: DnaPool { codec: Ternary, redundancy: 3x, profile: Illumina }
pool backup: DnaPool { codec: Ternary, redundancy: 2x, profile: Illumina }

fn archive_with_fallback() returns Result<DnaFile, Str> {
    let attempt: Result<DnaFile, Str> = store "sample_a.txt" into primary
    let saved: DnaFile = attempt?
}

This is the one place NucleScript's execution model stopped being "compile a static plan, then replay it as-is": a Result-returning function's body now actually executes, statement by statement, when called — everything else in the language (including if/for above) still resolves entirely at compile time. A bare statement-form store/retrieve/delete inside a function body executes for real too (not just at the top level), and a File/Str-typed parameter carries its real argument value at runtime — Ok(<expr>)/Err("...") construct a Result directly, and compose with match/? (nested match, ? applied straight to a match expression, Ok/Err as match-arm bodies). See the "Result / Error Propagation" section of the grammar reference for the full semantics and docs/examples/result_fallback_store.nsl for a complete, runnable example.

A function can be generic over Pool<T>'s profile — one function instead of a hardcoded copy per Illumina/Nanopore/Twist:

pool illumina_archive: DnaPool { codec: Ternary, redundancy: 3x, profile: Illumina }
pool nanopore_archive: DnaPool { codec: Ternary, redundancy: 3x, profile: Nanopore }

fn recover_from<P>(source: Pool<P, 0.35%>) returns Pool<Recovered> {
    let recovered: Pool<Recovered> = consensus_vote(source, coverage: 10x)
}

let noisy_illumina: Pool<Illumina, 0.35%> = simulate illumina_archive under Illumina
let recovered_a: Pool<Recovered> = recover_from(noisy_illumina)

let noisy_nanopore: Pool<Nanopore, 5%> = simulate nanopore_archive under Nanopore
let recovered_b: Pool<Recovered> = recover_from(noisy_nanopore)

recover_from is type-checked once, treating P as an opaque placeholder; each call site unifies P against its own argument's real profile — no runtime representation, no per-instantiation re-checking of the body. An explicit name::<Illumina>(...) type argument resolves the one case inference alone can't: a type parameter that appears only inside a Fn(...)-typed parameter's own signature, never as a directly Pool<P>-shaped argument. See the "Generics" section of the grammar reference for the full semantics and docs/examples/generic_pool_recovery.nsl/ docs/examples/explicit_type_args_and_file_param.nsl for complete, runnable examples.

A caught Result can now be branched on directly with match, instead of needing a second, independent function call from the caller:

pool primary: DnaPool { codec: Ternary, redundancy: 2x, profile: Illumina }
pool secondary: DnaPool { codec: Ternary, redundancy: 2x, profile: Illumina }

fn archive_with_fallback() returns Result<DnaFile, Str> {
    let attempt: Result<DnaFile, Str> = store "sample_a.txt" into primary
    let saved: DnaFile = match attempt {
        Ok(file) => file,
        Err(reason) => (store "sample_b.txt" into secondary)?
    }
}

Result used to be the only sum type in the language, closed to exactly two variants with a fixed Ok-then-Err arm order. NucleScript now also has real user-defined enums, and match is one general engine that handles both:

enum RecoveryPlan {
    Retry,
    Fallback,
    GiveUp(Str),
}

let plan: RecoveryPlan = RecoveryPlan::Fallback

fn archive_with_plan(plan: RecoveryPlan) returns Result<DnaFile, Str> {
    let attempt: Result<DnaFile, Str> = store "sample_a.txt" into primary
    let saved: DnaFile = match attempt {
        Ok(file) => file,
        Err(reason) => match plan {
            Retry => (store "sample_a.txt" into primary)?,
            _ => (store "sample_b.txt" into secondary)?,
        }
    }
}

Arm order is now free (checked by variant name, not position), and exhaustiveness is enforced — every declared variant needs an arm, or a trailing wildcard _ covers the rest. Result's own Ok/Err construction syntax and runtime representation stay untouched and distinct from a user enum's — the unification lives entirely at the matching layer, not construction or storage. See the "Enums"/"Pattern Matching" sections of the grammar reference for the full semantics (including the one still-real limitation: Err(...)'s payload must always be a literal string, even inside a user-enum match arm) and docs/examples/match_result_fallback.nsl/ docs/examples/recovery_plan.nsl for complete, runnable examples.

Functions can now be anonymous, bound to a variable, and passed as arguments — real closures, with real lexical capture:

fn retry_once(attempt_fn: Fn() -> Result<DnaFile, Str>) returns Result<DnaFile, Str> {
    let attempt: Result<DnaFile, Str> = attempt_fn()
    let saved: DnaFile = match attempt {
        Ok(file) => file,
        Err(reason) => attempt_fn()?
    }
}

Capture is by snapshot, and that's simply correct here, not a design compromise: every let binding in NucleScript is single-assignment, so there's no "later mutation" a by-value/by-reference distinction could ever observe. Closures can now be generic (fn<T>(...), when nested inside an already-generic enclosing scope) and self-recursive (a let-bound closure can call itself by its own bound name), and nucle plan/nucle explain now narrate through a let-bound closure's own call. See the "Closures" section of the grammar reference for the full semantics (including the honest limits still standing: no mutual recursion between two independently-let-bound closures, and narration still can't see through a Fn(...)-typed parameter's call) and docs/examples/closure_retry.nsl for a complete, runnable example.

A Fn(...)-typed parameter can now declare the effect ceiling its call is trusted to have, closing the one real gap closures left open: calling a parameter (as opposed to a let-bound closure) couldn't have its effect analyzed at all before, since the concrete closure a caller passes isn't knowable until runtime — silently treated as Pure even if it turned out to be genuinely destructive.

fn archive_with_cleanup_policy(cleanup: Fn() -> Void confirm physical_key) returns Void {
    let x: Void = cleanup()
}

No annotation (every Fn(...) type written before this, and any written without one going forward) means exactly the pre-existing behavior — this is purely additive, opt-in syntax. Soundness comes from checking every concrete closure at the point it's bound into an annotated slot (an argument, or a let), not at the parameter's own call site — which is what makes trusting the declared ceiling there sound, including through a captured outer parameter or a parameter forwarded straight through to another compatibly-annotated one. See the "Effect-Annotated Function Types" section of the grammar reference for the full semantics and docs/examples/effect_annotated_closure.nsl for a complete, runnable example.

For ecosystem growth, the compiler also exposes stable integration surfaces: built-in preset imports, a serializable playground analysis API, and hardware bridge request extraction for effectful plans.

import {
    medical_archive,
    reliable_store as store_recipe,
    illumina_recovery
} from "nuclescript/presets"

Four official packages ship with this repository and are published to the Nuclescript org's package registry, each versioned independently from NucleOS releases:

Package Import source Purpose
@nuclescript/presets nuclescript/presets Baseline archive pool schemas, a reliable-store pipeline, and an archive_with_guarantee function
@nuclescript/profiles nuclescript/profiles Illumina/Nanopore/Twist pool presets at optimizer-recommended redundancy, plus per-profile simulate functions
@nuclescript/benchmarks nuclescript/benchmarks Pool schemas and pipelines matching the docs/examples/fixtures/ workload set
@nuclescript/recovery nuclescript/recovery Consensus/recovery pool bindings and a recover_with_consensus function

Each package's manifest, source, README, and changelog live under packages/nuclescript-<name>/, with a registry index at packages/registry.json — the CLI resolves packages by reading that file directly, so adding an entry there is what makes a new package discoverable. List or inspect bundled packages with:

nucle packages                          # quick listing of the bundled presets package
nucle package list                      # full registry.json index
nucle package inspect "@nuclescript/profiles"

Install and verify packages by name (resolved against packages/registry.json, not a filesystem path):

nucle package install "@nuclescript/presets"
nucle package lock                      # write/update nucle.lock with manifest + source checksums
nucle package verify "@nuclescript/presets"   # checks manifest shape + checksum against nucle.lock

Current NucleScript result summary:

Program Payload Data strands Parity strands Total strands Nucleotides Avg strand Redundancy Result
docs/examples/store.nsl 31 B 2 4 6 3156 nt 526 nt 3.00× Stored via VFS
docs/examples/pipeline_backup.nsl 31 B 2 4 6 3156 nt 526 nt 3.00× Exact roundtrip
docs/examples/sequence_literals.nsl Compile-time DNA validation
docs/examples/probabilistic_recovery.nsl - - - - - - - Compile-time error-budget propagation
docs/examples/effect_confirmations.nsl - - - - - - - Effect confirmation and planning
docs/examples/preset_imports.nsl - - - - - - - Built-in preset import validation
docs/examples/control_flow.nsl 31 B 2 4 6 3156 nt 526 nt 3.00× Compile-time if/for desugaring, then stored via VFS
docs/examples/result_fallback_store.nsl 31 B + 30 B 4 5 9 4248 nt 472 nt 2.25× Result<T, E>/?: a real VFS failure caught inside a function instead of aborting the run; Ok(...)/Err(...) constructors
docs/examples/generic_pool_recovery.nsl - - - - - - - Generics: fn recover_from<P>(...) called with both Pool<Illumina> and Pool<Nanopore>, an explicit ::<Illumina>() type argument, and a generic closure nested inside a generic function
docs/examples/match_result_fallback.nsl 31 B ×2 + 30 B ×2 8 8 16 7120 nt 445 nt 2.00× Pattern matching: match's Ok arm stores directly, its Err arm's fallback store lands on the second call; nested match and ? on a match expression
docs/examples/closure_retry.nsl 31 B ×2 + 30 B + 39 B 8 8 16 7120 nt 445 nt 2.00× Closures: a higher-order retry_once genuinely calls its closure argument twice on a caught failure; a captured-binding closure's fallback lands; a self-recursive closure retries into a different fallback target and terminates
docs/examples/explicit_type_args_and_file_param.nsl 30 B ×2 4 4 8 3560 nt 445 nt 2.00× recover_generically::<Illumina>(...) resolves a type parameter inference alone can't, and a File-typed parameter's real filename flows into a statement-form store
docs/examples/recovery_plan.nsl 31 B ×2 4 4 8 3560 nt 445 nt 2.00× A user-defined enum RecoveryPlan, a nested match (a user-enum match inside a Result match's Err arm), and exhaustiveness via a trailing wildcard _
docs/examples/effect_annotated_closure.nsl - - - - - - - Effect-annotated Fn(...) -> Void confirm physical_key: a confirmed destructive closure passed through two layers of function calls actually stores then deletes sample_a.txt for real, and nucle doc correctly reports Destructive (confirmed) instead of the previous, silently wrong Pure

Compiler diagnostics are surfaced before execution. For example, docs/examples/critical_redundancy_warning.nsl warns when critical data uses only 1x redundancy.

nucle check runs lex → parse → typecheck without touching hardware or executing anything — the fast path for CI or an editor integration. Every diagnostic carries a real file:line:column (threaded from the lexer's token positions through the parser's AST and into the type checker), a stable error code, and a rustc-style source snippet — not just a message with no source location to jump to. See docs/errors.md for the full list of codes:

$ nucle check docs/examples/failures/missing_confirmation.nsl
docs/examples/failures/missing_confirmation.nsl:11:1: error [E-DELETE-UNCONFIRMED]: delete 'old_archive.bin' from 'archive' has Destructive effect and requires explicit physical key confirmation
   |
11 | delete "old_archive.bin" from archive
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

nucle explain goes further, turning MIR optimizer notes and the program's full effect summary (including effects propagated through function calls — calling a function that deletes something isn't automatically safe just because it's wrapped in a function) into plain-language explanations. See docs/effects.md for the full effect model:

$ nucle explain docs/examples/critical_redundancy_warning.nsl
--- Execution & Safety Explanation ---

### Optimization Decisions:
- optimiser raised redundancy for 'sample_a.txt' from 1x to 4x under Illumina. Redundancy was increased to satisfy statistical recovery guarantees under this profile's specific error profile.

### Safety & Confirmation Summary:
- pool 'archive' (Pure): Pure effect. [SAFE (Pure)]
- store 'sample_a.txt' (Synthesis): Synthesis effect. [CONFIRMED]

Editor Support

A VS Code extension lives at editors/vscode/nuclescript/ — syntax highlighting for .nsl files (keywords, types, profile/codec constants, strings, and the 3x/99.5%/date/size-in-bytes number forms lexer.rs actually recognizes), derived directly from the real grammar so it can't highlight a token the compiler would reject. A snapshot test (npm test inside that directory) tokenizes every file in docs/examples/ and diffs against committed snapshots, so a compiler keyword change that isn't mirrored in the grammar shows up as a CI-visible diff instead of silently going stale.

The extension also spawns a real language server — nucle_lsp — over stdio, so .nsl files get live diagnostics (the same errors/warnings and error codes nucle check reports, as you type), hover (pool/function/strand/sequence/binding signatures), go-to-definition, and a document outline. nucle_lsp is a thin protocol adapter over nucle_lang::analyze — it never duplicates compiler logic, verified by an integration test that speaks the real Content-Length-framed JSON-RPC protocol over an in-memory pipe and cross-checks published diagnostics against nucle check's own output for the same source. Build it with cargo build -p nucle_lsp --release. Autocomplete, rename, and semantic tokens aren't built yet.

Beyond editing, the extension can format (Format Document/format on save, via nucle-cli fmt) and actually run a program (NucleScript: Run File — a ▷ button, Ctrl+F5/Cmd+F5, or the Explorer context menu — via nucle-cli run, with output in an integrated terminal). Neither needs a local Rust toolchain: nucle-lsp and nucle-cli are each resolved on PATH first, then downloaded once from GitHub Releases and cached (src/serverDownload.ts, src/cliDownload.ts) if not found — installing the extension is enough to write, check, format, and run a .nsl file.

The extension is published on the VS Code Marketplace — icon, changelog, license, a .github/workflows/release-vscode-extension.yml that builds nucle-lsp for Windows/Linux/macOS (x64 + arm64) and attaches them to a GitHub Release, and an in-extension downloader so a marketplace install works without a local Rust toolchain. See the extension's own CONTRIBUTING.md for how to ship an update.

Playground

🧪 Try it live in your browser — no install, no download. nucle_wasm compiles the same compiler/codec/ECC engine to WebAssembly and runs it entirely client-side; a GitHub Actions workflow (Nuclescript/playground's .github/workflows/pages.yml) rebuilds and redeploys it on every push, so it's always current.

The playground has three tabs, each backed by the real engine (no reimplemented math, no mocked data):

  • Write & Run — the same analyze_source API nucle check --json uses internally. Paste a .nsl program, get diagnostics, simulation steps, and optimizer notes.
  • Benchmark Explorer — pick a codec/profile, drag the redundancy slider, and density/GC%/cost/recovery-probability update live — computed by nucle_codec::benchmark plus a real Reed-Solomon-aware Monte-Carlo recovery estimate, not a lookup table.
  • Pipeline Visualizer — encodes real input through the actual codec/ECC/noise engine and animates each strand through encode → synthesize/sequence (noise) → recover, including honest failures when redundancy/profile can't reconstruct the data.

Prefer a native server over the browser build? nucle_playground is the same three tabs as a self-contained tiny_http server:

cargo run -p nucle_playground
# open http://127.0.0.1:8080

It's also published standalone at Nuclescript/playground — a self-contained snapshot of this workspace (verified to build independently from a fresh clone) for anyone who wants to run the playground without cloning this repo directly. For zero setup at all (no cargo, no cloning), grab a prebuilt binary from its Releases — Linux/Windows/macOS builds with the frontend embedded, so downloading and running the single file is enough.


CLI Usage

Every command also accepts a global --json flag (e.g. nucle --json pool) for machine-readable output.

Pool storage location

nucle store/retrieve/migrate/search/pool/list/capacity/audit/ confirm-users/encrypt-pool/decrypt-pool/scan/agent persist to a real pool directory, so nucle store followed by a separate nucle retrieve invocation genuinely works — not just within one process's own memory. Resolved in priority order: an explicit --pool-dir <path> flag, then the NUCLEOS_POOL_DIR environment variable, then a project-local .nucleos/ directory next to wherever the command runs (created on first use, like .git/ — already in this repo's own .gitignore). State is written atomically (a temp file, then renamed over the real path), so a process killed mid-write never corrupts previously-stored data.

A global --tenant <name> flag (or NUCLEOS_TENANT) scopes any command to that tenant's own, fully isolated pool — a tenants/<name>/ subdirectory of whatever the above resolves to, with its own independent state.json/ audit.log/key.json/config.json. Omit it and everything behaves exactly as before tenancy existed. See the usage examples below and nucle tenants to list which tenants already have data.

# Encode a file to DNA strands
nucle encode myfile.txt -o myfile.dna

# Decode DNA strands back to binary
nucle decode myfile.dna -o recovered.txt -s 1024

# Store a file with error correction (4 parity strands)
nucle store myfile.txt -r 4

# Retrieve a stored file
nucle retrieve myfile.txt

# Migrate a stored file to new parameters (redundancy and/or codec)
nucle migrate myfile.txt -r 6
nucle migrate myfile.txt --codec ternary-rotating-cipher

# Search for files
nucle search "name:readme type:txt"

# Pool statistics
nucle pool

# Store under a path-like name and list by prefix -- "docs/readme.txt" and
# "downloads/readme.txt" don't collide, since a relative source path keeps
# its directory prefix as part of the pool's own (still flat) namespace
nucle store docs/readme.txt
nucle list docs/

# Show the pool's capacity limit and current usage (omit both args to just report)
nucle capacity

# Set a capacity limit (in total nucleotides) -- writes past it are refused
# up front with a clear "pool capacity exceeded" error, before touching the
# codec/ECC pipeline; the limit persists and is enforced by later invocations
nucle capacity 1000000

# Clear the limit (unlimited growth, today's default behavior)
nucle capacity --unlimited

# Show the pool's audit log -- every store/retrieve/delete event, oldest
# first, including failed ones (e.g. a retrieve for a file that isn't there)
nucle audit

# Only the most recent 20 events
nucle audit --tail 20

# Simulate synthesis noise (Illumina profile)
nucle simulate myfile.txt -p illumina

# Benchmark all codecs in isolation (density, GC, homopolymers, recovery probability, cost)
nucle bench --profile nanopore

# Full-pipeline benchmark against standard fixtures (write → simulate → read)
nucle benchmark -p illumina -r 4

# Stress test: sweep all codecs across data distributions
nucle stress -s 256

# Full-pipeline stress test: encode → noise → ECC → recover across N files
nucle pipeline -f 100 -s 1024 -p illumina -c 10 -r 4

# Run a NucleScript source file
nucle run docs/examples/store.nsl

# Compile-only validation: lex -> parse -> typecheck, no hardware, no execution
nucle check docs/examples/store.nsl
nucle check docs/examples/store.nsl --json

# Explain effect summary and optimizer decisions in plain language
nucle explain docs/examples/critical_redundancy_warning.nsl

# Format a NucleScript source file in its one canonical style (gofmt-style,
# zero configuration). Prints to stdout by default.
nucle fmt docs/examples/store.nsl
nucle fmt docs/examples/store.nsl --write     # rewrite the file in place
nucle fmt docs/examples/store.nsl --check     # exit non-zero if not already formatted (for CI)

# Run test { ... } blocks (assert reuses if's comparison/boolean operators,
# evaluated at compile time -- see docs/grammar.md#testing-test--assert)
nucle test docs/examples/archive_fn.nsl
nucle test docs/examples/archive_fn.nsl --json

# Generate Markdown docs from /// doc comments on pool/strand/seq/fn/pipeline
nucle doc docs/examples/archive_fn.nsl
nucle doc docs/examples/archive_fn.nsl --output archive_fn.md

# Scaffold a new NucleScript project (nucle check/run succeed against it unmodified)
nucle new my-project
cd my-project && nucle run main.nsl

# Show an optimized no-hardware NucleScript plan
nucle plan docs/examples/probabilistic_recovery.nsl

# List released NucleScript packages / inspect the full registry
nucle packages
nucle package list

# Install, lock, and verify packages by name against packages/registry.json
nucle package install "@nuclescript/presets"
nucle package lock
nucle package verify "@nuclescript/presets"

# Export a compiled program's synthesis/sequencing/destructive requests.
# Requires --confirm whenever the batch is cost-bearing or destructive.
nucle hardware export docs/examples/effect_confirmations.nsl --confirm -o batch.json
nucle hardware export docs/examples/effect_confirmations.nsl --confirm --provider mock

# Multiple sources submit concurrently instead of one at a time
nucle hardware export a.nsl b.nsl c.nsl --confirm --provider mock-delayed --simulated-delay-ms 300

# Optionally restrict which OS users may pass --confirm for this pool's
# hardware exports -- an allowlist check on top of --confirm itself.
# Unconfigured (the default) means unrestricted, matching today's behavior.
nucle confirm-users                    # show the current allowlist
nucle confirm-users --add alice        # add a user
nucle confirm-users --remove alice     # remove a user

# Encrypt a pool's stored state at rest, protected by a passphrase --
# --pool-key (or NUCLEOS_POOL_PASSPHRASE) is required for both enabling
# it and for every later command against an encrypted pool
nucle --pool-key "correct horse battery staple" encrypt-pool
nucle --pool-key "correct horse battery staple" retrieve readme.txt
nucle --pool-key "correct horse battery staple" pool         # shows "Encrypted: yes"

# Decrypt it back to plaintext (needs the pool's current passphrase)
nucle --pool-key "correct horse battery staple" decrypt-pool

# Proactively scan for silent corruption, instead of only discovering it
# passively the next time something happens to retrieve a given file --
# attempts a real recovery of every file and reports which ones failed;
# exits non-zero if anything's corrupted, for scripting/CI
nucle scan
nucle scan docs/          # only files under a given prefix

# Serve this pool's current state as Prometheus metrics over HTTP --
# re-read fresh on every scrape at GET /metrics, so a real Prometheus
# server can track it over time. A small, separate exporter process; it
# never handles store/retrieve/etc. itself -- those still only ever run
# as direct CLI invocations against the same pool_dir. Runs until killed.
nucle serve                              # http://127.0.0.1:9898/metrics
nucle serve --port 9100 --bind 0.0.0.0   # a different port, exposed beyond localhost

# Scope any command to a named tenant's own, fully isolated pool -- a
# tenants/<name>/ subdirectory with its own independent state, never
# sharing storage with the base pool or any other tenant
nucle --tenant acme store readme.txt
nucle --tenant acme retrieve readme.txt
nucle --tenant globex list                 # globex never sees acme's files
nucle tenants                              # list every tenant with existing data

# Environment and integrity diagnostics
nucle doctor

# Natural language agent
nucle agent "store readme.txt with 3x redundancy"
nucle agent "search for text files"
nucle agent "pool status"

Test Coverage

Crate Tests What's Tested
nucle_codec 62 (+3 doctests) Nucleotide types, constraints, ternary codec (incl. a regression test proving the fixed strand-index header roundtrips past its old 81-strand capacity, and a test confirming encoding fails loudly instead of silently corrupting once truly past the new, much larger capacity), fountain codec, yin-yang codec, byte↔4-base packing roundtrip, benchmarks incl. GC distribution and homopolymer violation counts
nucle_synth 32 Error models, noise engine, hardware profiles, encode→noise→decode e2e
nucle_ecc 39 Reed-Solomon (incl. combined error-and-erasure Berlekamp-Welch decoding, blind single-strand correction, parity-reindexing regression), fountain erasure, repair pipeline, per-position observed error distribution, partial-order-alignment consensus (frame-shifting indels, boundary insertions outvoted by majority, fold-order-independence, realistic-noise fuzz crash safety)
nucle_index 31 Primers (incl. edit-distance-tolerant boundary matching under indel noise), CRISPR sim, vector index, metadata-similarity search
nucle_vfs 120 (+1 ignored) Pool, file, catalog, storage manifests, content-addressed archive IDs, rewrite/versioning (rewriting an existing filename versions instead of erroring, dna_history lists every version oldest-first ending with the current one, dna_read_version reads a specific past version, dna_delete removes the entire version chain not just the current version, a superseded version's search entry is dropped so search only ever surfaces the current one, plus Catalog-level coverage of archiving/retrieving/mutating/removing versions directly), migration (incl. codec-migration rejection), per-object recovery manifests, regression-pinned fixture roundtrips, Illumina noise roundtrips, and Nanopore noise roundtrips under the correct Twist-synthesis + Nanopore-sequencing pairing (recovers exactly at 15x coverage / 6 parity strands, always-run; a #[ignore]d thorough check reproduces the exact 50x/12 scale a since-fixed noise-doubling bug once made this look like an open research problem -- see docs/architecture.md; run it explicitly with cargo test -p nucle_vfs -- --ignored), durable cross-process persistence (NucleOS::open/persist: a fresh directory behaves like new, a stored file survives a real reopen, search is correctly rebuilt from the restored catalog, a deleted file's primer is never reassigned after reopening, and a stray partial .tmp file is never loaded over a good state.json), the hierarchical path-like namespace (Catalog::list_prefixed, same leaf name under different prefixes never colliding), optimistic-concurrency + file-lock safety (a stale persist after a concurrent write is rejected not silently lost, the same racing against a brand-new pool, sequential persists on one instance both succeed, and a pre-versioning state.json with no version field still loads), pool capacity limits (unlimited by default, an oversized write refused before touching the pool with the exact remaining headroom reported, a fitting write still succeeding, the limit persisting and being enforced after a real reopen, and clearing it removing the restriction), the system-wide audit log (pool_dir/audit.log: an ephemeral in-memory instance logs nothing, a successful write/read/delete each append their own event, a failed operation still logs with no archive ID, a migration's read+delete+write trail appears as three linked events under the same filename, and a truncated trailing line from a crash mid-append is skipped rather than failing the whole read), the --confirm OS-user allowlist (an empty/unconfigured allowlist allows any user, a configured one only allows listed users, save/load round-trip through pool_dir/config.json, and an unconfigured pool's check is a no-op), encryption at rest (nucle_vfs::crypto's Argon2id-wrapped, ChaCha20-Poly1305-encrypted data key: wrap/unlock round-trips, a wrong passphrase and a corrupted/tampered ciphertext both fail clearly, two key files for the same passphrase are never identical, encrypt/decrypt byte round-trips, a reused plaintext never produces the same ciphertext twice; plus NucleOS-level coverage of enable_encryption/open_encrypted/disable_encryption: a stored file survives an encrypt-then-reopen-then-decrypt round trip, the on-disk state.json no longer contains a stored file's plaintext filename once encrypted, a plain open() refuses an encrypted pool instead of misreading it, the wrong passphrase is refused clearly, open_encrypted is a transparent passthrough for an unencrypted pool, and enabling/disabling encryption twice in a row are both clear errors rather than a silent no-op), proactive integrity scanning (dna_scan: an empty pool reports zero files, a healthy pool scans every file as recoverable with matching strand counts, a file whose strands vanished from the pool without going through dna_delete scans as corrupted while an unaffected sibling file still scans healthy, prefix filtering matches dna_list's own convention, and a scan persists recovery manifests exactly as a real retrieve would), and Prometheus metrics (nucle_vfs::metrics: an empty pool collects zeroed metrics with no audit events, a stored file is reflected in both the pool-state gauges and the audit-event counts, a failed operation is counted separately from successes under the same (operation, success) label pair, the rendered Prometheus text carries a # HELP/# TYPE pair for every metric, and an unset capacity limit omits that one gauge entirely rather than emitting a bogus zero)
nucle_agent 35 Tool defs, planner, executor, including natural-language migrate (redundancy/codec extraction with a friendlier-than-the-CLI codec alias table) and help (regression-guarded against a fixed bug where it silently ran pool_status instead)
nucle_lang 247 Lexer (incl. /// doc comments as real, distinct tokens, rejected with a clear parse error anywhere they can't attach), parser, biological checks, sequence literals, probabilistic pool typing, effects (incl. propagation through function calls, if/for branches, ? short-circuits, and built-in consensus_vote/protect calls), compile-time if/for desugaring with comparison/boolean operators, consensus_vote/protect resolved as ordinary stdlib FunctionTable entries (arity/effects/"did you mean" parity with user functions), canonical formatter (nucle fmt, idempotence + parsed-program-equivalence over every shipped example, doc-comment-aware), test/assert test runner (nucle test: compile-time assertion evaluation shared with if, real per-test VFS execution, compile-error-vs-test-failure separation), Markdown doc generation from /// comments (nucle doc), MIR optimizer, simulation backend, table-driven package registry (all 4 official packages), lock file checksums, hardware request collection (incl. read-only Qc/Recovery request kinds derived from verify roundtrip/consensus_vote, proven to never require --confirm), VFS lowering, function declarations/calls, source spans + stable error codes + "did you mean" suggestions, symbol table for tooling, nucle check/nucle explain integration tests, Result<T, E>/? (parsing, typeck validity rules, conservative effect-joining across a ? short-circuit, and a golden-file regression suite proving zero behavior change for programs that use none of it), generics over Pool<T>'s profile (call-site unification, type-parameter conflict/unresolved detection, and a formatter regression test proving noisy < 0.1-style comparisons aren't mistaken for a generic angle-bracket list), closures/higher-order functions (real lexical capture, a genuinely higher-order call retrying twice on a caught failure, effect analysis correctly resolving a called closure's real body, the existing arity/type-mismatch/undeclared-function paths proven unaffected), a gap-closing pass over earlier Result/generics/pattern-matching/closures work (Ok(...)/Err(...) constructors and composability with nested match/?, explicit ::<Illumina>() type arguments, real statement-form execution and real File/Str-typed parameter values inside function bodies, generic closures, a self-recursive closure actually terminating for real against a live VFS, and nucle plan/nucle explain narration reaching into a let-bound closure's own body), user-defined enums + a general N-arm pattern-matching/exhaustiveness engine (one diagnostic code per new E-ENUM-*/E-MATCH-* failure mode, free arm order, a real user-flagged nested-match limitation fixed, Result/Ok/Err re-run unmodified through result_backward_compat.rs's golden-file suite proving zero behavior change from the unification), and effect-annotated Fn(...) function types (the new E-FN-EFFECT-ARG-MISMATCH code, positive/negative/capture/forwarding scenarios, white-box tests proving a populated fn_param_effects table resolves an otherwise-unresolvable call to its declared ceiling, and a regression test for a separately-found, real bug where a top-level call to a Void-returning function with statement-form side effects never actually executed them)
nucle_hardware 49 Confirmation gating (effectful/destructive rejection, count/message correctness, sync and async variants, and a dedicated proof that read-only Qc/Recovery requests never count as effectful), mock provider dry runs, file-export JSON roundtrip and field preservation, parent-directory creation, the job-handle/concurrency model (submit() returning before a real simulated delay elapses, multiple concurrent jobs finishing in well under their sequential-sum time, Pending/Running/Complete status transitions), and the real vendor adapters: Twist/IDT/Illumina HTTP request-building/auth-header/status-classification against a local tiny_http stand-in server (order-then-poll round trips, completed/cancelled/aborted status mapping, a bounded-timeout failure against an unreachable endpoint), IDT's two-step OAuth2 token-then-order flow specifically, and Nanopore's ProtocolState-to-JobStatus mapping plus a real (but connection-refused) gRPC dial against an absent MinKNOW instance
nucle_blockdev 22 The BlockDevice trait's own contract (out-of-range/wrong-size/never-written rejection) against both backends; the Atlas/imec-modeled SynthesisArrayBlockDevice's write-once enforcement, a real write-then-read-recovers-after-direct-strand-corruption proof (consensus + Reed-Solomon, not luck from a low noise seed), and batched writes measurably saving latency over one-at-a-time (compared against a same-config sequential baseline, not a fixed wall-clock number, so it isn't flaky under CI/parallel-suite load); the Mizzou-modeled RewritableNanoporeBlockDevice's genuine overwrite-in-place (the core behavioral difference from the write-once backend), the same corruption-recovery proof, and per-operation latency on both reads and writes; and blob's minimal length-prefixed multi-block layout round-tripping unmodified across both backends, including an empty blob and a too-large blob correctly rejected before any block is touched
nucle-cli 34 Cross-process pool persistence, the hierarchical namespace, real concurrent-process safety, pool capacity limits, the audit log, the --confirm OS-user allowlist, encryption at rest, proactive integrity scanning, the Prometheus metrics exporter, and per-tenant pool isolation, spawning the real, compiled nucle-cli binary per test (CARGO_BIN_EXE_nucle-cli, not an in-memory roundtrip): store then retrieve in separate processes, a clean not-found error with no prior store, a stray partial .tmp file never corrupting a later real invocation, docs/readme.txt/downloads/readme.txt storing and listing independently by prefix, an absolute source path still storing under its bare leaf name, two genuinely concurrent store processes racing on the same pool never silently losing a write, a small persisted capacity refusing an oversized store in a later separate invocation with no catalog entry created, clearing the limit letting a previously-refused write through, store/retrieve/a failing retrieve each showing up in nucle audit's output across separate invocations, --tail correctly limiting to the most recent events, an empty pool reporting no events rather than an error, an unconfigured pool letting --confirm through for any OS user, a configured allowlist refusing it for every other user and allowing it again once the real, live invoking user (read from the environment at test time, never hardcoded) is added, removal taking a user back off the list, a Pure-only (no effectful requests) batch never consulting the allowlist at all, a stored file surviving encrypt-pool → reopen-with-passphrase → decrypt-pool, a plain (no --pool-key) invocation refused once encrypted, a wrong passphrase refused clearly, NUCLEOS_POOL_PASSPHRASE working exactly like --pool-key, a passphrase given to an unencrypted pool being a harmless, explicitly-noted no-op, a healthy pool's scan exiting zero, a state.json silently corrupted directly on disk (every "A" base flipped to "T", the same raw-file-mutation style as the stray-.tmp test) scanning as corrupted with a non-zero exit, prefix filtering only covering matching files, nucle serve's real /metrics endpoint (spawned as a genuine long-running child process, talked to over a raw TcpStream -- no HTTP-client dependency needed for one GET) reporting real pool state, a file stored while the server is already running showing up on the very next scrape with no restart needed, //an unknown path/a non-GET method each responding sensibly, two tenants storing under the same --pool-dir never seeing each other's files, the untenanted base pool staying completely empty regardless of tenant activity, nucle tenants listing exactly the tenants with data (sorted, none before any exist), NUCLEOS_TENANT working like --tenant, and an invalid tenant name (a path separator, ..) rejected before it ever becomes a path component
nucle_lsp 11 Word-at-cursor resolution, hover/definition lookup, and a real Content-Length-framed JSON-RPC integration test (diagnostics, hover, go-to-definition) cross-checked against nucle check's own output
nucle_demo_core 5 Interactive benchmark/pipeline demo engine: end-to-end recovery estimation, unknown-codec/oversized-input rejection
Total 667 (+3 doctests, +1 ignored) End-to-end: binary → DNA → noise → ECC → recover → binary

Project Structure

nucle_codec/     — Encoding/Decoding engine (binary ↔ ATCG)
nucle_synth/     — Synthesis simulator (hardware mock)
nucle_ecc/       — Error correction (Reed-Solomon, fountain, consensus)
nucle_index/     — Retrieval & indexing (CRISPR-sim, vector index)
nucle_vfs/       — Virtual file system (syscall-style API, storage/recovery manifests, migration)
nucle_agent/     — Agent interface (ReAct planner)
nucle_lang/      — NucleScript compiler, MIR optimizer, package registry, lock files, ecosystem APIs, simulation backend, and VFS backend
nucle_hardware/  — Hardware provider adapters (Provider trait with submit()/JobHandle, MockProvider, DelayedMockProvider, FileExportProvider, and real Twist/IDT/Illumina/Nanopore vendor adapters)
nucle_lsp/       — NucleScript language server (tower-lsp adapter over nucle_lang::analyze: diagnostics, hover, go-to-definition, document outline)
nucle_cli/       — Command-line interface
nucle_playground/ — Interactive web playground (tiny_http server + static frontend), also published at github.com/Nuclescript/playground
nucle_demo_core/ — Shared, I/O-free benchmark/pipeline-visualizer logic used by both nucle_playground and nucle_wasm
nucle_wasm/      — Same playground compiled to WebAssembly; live at nuclescript.github.io/playground
editors/vscode/nuclescript/ — VS Code extension: TextMate grammar + language server client
docs/            — Architecture notes, paper references, and runnable examples/fixtures
packages/        — NucleScript package registry (packages/registry.json) and package releases (presets, profiles, benchmarks, recovery)

Contributing

Contributions are welcome — see CONTRIBUTING.md for the dev environment setup, code style, and how to open a pull request. Please also read the Code of Conduct. Found a security issue? See SECURITY.md instead of opening a public issue.

License

MIT — see LICENSE for details.

About

A software-defined DNA storage operating system — encode, protect, and retrieve real files as synthetic DNA, with its own domain-specific language (NucleScript), a CLI/VFS runtime, error correction, encryption, and Prometheus metrics.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages