DNA Engine is a direct-swiftc SwiftUI macOS application with UI-free analysis engines, generated local resources, an encrypted single-workspace store, and no production networking API. It targets macOS 14 or later and is built as one app executable plus Apple system frameworks.
The product is private and research-oriented. Architecture must enforce, not merely label, these limits: no cloud path, no diagnostic or prescribing output, no inferred kinship, no guessed strand orientation, and no destructive replacement before validation succeeds.
User-selected source bytes
|
v
GenomeSourceDecoder
content signature + hard limits + archive validation
|
v
GenomeParser -----> immutable GenomeImportReceipt
format + VCF sample + reconciled row outcomes
|
v
ParsedGenome -> GenomeStats -> AnalysisEngine -> AnalysisReport -> Views / ReportExport
|
+---- memory-only private session
+---- memory-only synthetic demo
+---- memory-only secondary concordance file
|
`---- encrypted workspace mode
|
v
PrivateWorkspaceStore actor
|
v
PrivateGenomeVault -> workspace.dnavault
AES-256-GCM + ThisDeviceOnly Keychain key
Source bytes are parsed through GenomeParser.parse(data:sourceName:options:); encrypted restore therefore needs no plaintext temporary file. A primary result is published only after parsing and analysis succeed. In encrypted mode, the new vault is also written, reopened, authenticated, and compared with the intended envelope before UI state changes.
PrivateVault.swift defines two version layers:
- A five-byte external container header:
DNAVmagic plus container version. PrivateGenomeEnvelope.schemaVersioninside AES-GCM ciphertext.
The encrypted binary-plist envelope contains the original filename and extension, SHA-256 of the original source bytes, import date, vendor/build hints, parser/catalog versions, accepted/skipped counts, and the exact original payload. Except for magic and container version, metadata and source bytes are encrypted and authenticated together.
KeychainPrivateVaultKeyProvider creates a random 256-bit AES key only when sealing a new/replacement vault. The generic-password item uses service ai.shadowfetch.dnaengine.private-vault, account workspace-master-key-v1, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, and kSecAttrSynchronizable = false. Opening an existing vault is load-only: a missing key is an explicit error and never causes silent replacement-key creation.
ThisDeviceOnly is intentional. There is no cloud escrow, password recovery, or automatic transfer to another Mac; loss of the Keychain item makes the ciphertext unreadable by design.
PrivateGenomeVault.writeVerified:
- Rejects a non-file destination and an existing symlink/nonregular file.
- Creates the support directory and enforces mode
0700. - Seals with a fresh AES-GCM nonce.
- Writes a same-directory mode-
0600temporary file, synchronizes it, and atomically moves/replaces the destination. - Re-enforces
0600, reloads the final ciphertext, authenticates/decrypts it, validates schema and payload hash, and compares the complete envelope.
Legacy imported_genome.txt migration validates/parses the source first and removes plaintext only after the encrypted final file passes authenticated round-trip verification. Failure preserves the legacy source unchanged.
PrivateWorkspaceStore is an actor. Each primary operation receives a UUID generation and monotonically increasing sequence. Stale generations cannot save after a newer operation or erase. Verified erase increments the sequence and clears the active generation before deleting data, preventing cancelled work from recreating the vault.
DNAEngine cancels prior primary, comparison, and PDF tasks when replacing or clearing work. Status/progress publication is generation-checked on the main actor. A failed import leaves the previous encrypted workspace unchanged.
Private session, synthetic demo, and secondary comparison payloads are not persisted by their flows. clearSensitiveMemory releases parsed genome, report, receipt, selections, and comparison state. WorkspaceActivityMonitor observes local input events; App.swift checks inactivity every 30 seconds and clears state at the configured threshold (Never, 5, 15, 30, or 60 minutes; default 15). The encrypted vault remains available for explicit reload.
This is lifecycle minimization, not a forensic secure-RAM-zeroization guarantee.
GenomeImportFoundation.swift owns source descriptors, immutable receipts, limits, content decoding, and archive validation. GenomeParser.swift owns text-format interpretation and record accounting. Both are Foundation/zlib-only and compile in the UI-free macOS 14 check CLI.
Content magic identifies gzip and ZIP; extension is metadata and mismatch becomes a receipt notice. Standard limits are:
| Limit | Value |
|---|---|
| Input bytes | 256 MiB |
| Expanded bytes | 768 MiB |
| Expansion ratio | 200× |
| Stored diagnostic examples | 25 |
| Minimum accepted calls | 500 |
Native zlib decoding checks output continuously against limits. Gzip requires a complete single stream with checksum/trailer validation and no concatenated tail.
The strict ZIP reader accepts exactly one safe regular member using stored or raw-DEFLATE compression. It validates EOCD/central/local bounds, entry counts, flags, methods, filenames, extra fields, data descriptors, sizes, CRC, and end position. ZIP64, multi-disk, encrypted/AES, patched, masked, multi-entry, special-file, path-traversal, corrupt, concatenated, or unsafe-ratio inputs are explicit errors. The member is decoded in memory, never extracted.
The #CHROM header supplies sample names. GenomeImportOptions.selectedVCFSample supports an exact explicit name. Without one, the only sample is used or the first of multiple samples is selected with a visible notice; the current UI uses this default behavior.
Each row locates GT through FORMAT, then resolves only complete diploid 0, 1, and . alleles against single-base REF/ALT. Phased and unphased diploid SNPs are supported. Haploid/polyploid calls, allele indices beyond 1, indels, symbolic alleles, multiallelic records, non-PASS/. filters, malformed values, and unsupported identifiers are rejected rather than truncated.
GenomeParseDiagnostics reconciles:
totalRecords = acceptedRecords + skippedRecords
skippedRecords = duplicateRecords + conflictRecords + malformedRecords + unsupportedRecords
filteredRecords is an audited subset of unsupported records. noCallRecords is a subset of accepted records so QC can see explicit no-calls. Duplicate calls are retained once; conflicting duplicates never replace the first accepted call.
Tools/generate_knowledge.py is the sole producer of Resources/knowledge.json. Fourteen section modules under Tools/knowledge_sections/ register marker definitions; shared lints require citation pointer, tier, effect, transferability, and expected-chip metadata. Dedicated lints cover compounds, PGx definitions, limitations, and demo pins. A deliberately broken fixture must fail, proving the lint path is active.
The current resource contains 403 markers, 30 multi-locus rule definitions, 14 PGx gene definitions, 15 polygenic panels, 36 legacy ancestry-informative markers, 17 static limitations, and 109 hand-pinned demo genotypes.
ReportExport.dedupedCitations combines marker and compound citation records by exact (identifier, note) pair. Current generated count:
| Kind | Unique identifier-note pairs |
|---|---|
| Numeric PMID strings | 147 (90 distinct numeric IDs) |
| GWAS Catalog pointers | 273 |
| PharmGKB pointers | 38 |
| CPIC pointers | 28 |
| ClinVar pointers | 11 |
| Total | 497 |
The generator and harness enforce nonempty fields, exact deduplication, and count parity. They do not query PubMed or registries and therefore do not independently verify all 497 entries or their scientific claims.
MarkerMatcher permits direct or reverse-complement matching for resolvable definitions. A/T and C/G palindromic pairs are withheld as .strandAmbiguous before genotype interpretation. This rule also propagates into compound rules and PGx locus observations.
Core engines are deterministic and UI-free:
AnalysisEngineorchestrates marker observations, rules, panels, ancestry, ROH, QC, pathway/lifestyle context, and limitations.CompoundTraitsevaluates catalog rules with missingness/orientation gates; partial coverage is disclosed rather than filled in.PharmacogenomicsEngineemits locus-level observations and published drug-gene reference pairs. Compatibility fields remain structurally present but are fixed to indeterminate/typical; no star allele, diplotype, phenotype, medication response, alert, or action is inferred.ComparisonEnginereports direct raw autosomal genotype/allele overlap only.RelationshipDegreehas only.notInferred; no IBD or kinship model exists.PathwaysEngineoverlays coverage/status on static research maps and never computes activity, expression, concentration, flux, or outcome.LifestyleEngineproduces neutral evidence questions; no genotype-selected product, amount, restriction, schedule, exercise plan, or treatment.QCEngineperforms file forensics, including call quality, build sentinels, chip coverage, strand status, and sex-chromosome signal.ROHScannerreports observed-span homozygosity with qualified length-class context.HonestyEnginemerges 17 static limitations with file-specific missingness, coverage, and QC limitations.
Tools/build_reference_panel.py packages Resources/reference_panel.json from the validated staging panel. The packaged resource contains 2,621 markers and the 12 columns consumed at runtime: AFR, EUR, EAS, SAS, AMR, MID, NWE, TSI, IBS, FIN, ASJ, and NFE. Provenance notes survive packaging and appear in ancestry outputs.
AdmixtureEngine is the authoritative 2.1 two-tier supervised maximum-likelihood model:
- Tier 1 fits AFR/EUR/EAS/SAS/AMR/MID with deterministic fixed-iteration EM.
- Tier 2 fits NWE/TSI/IBS/FIN/ASJ on the within-European informative subset while holding non-European components at tier-1 values.
- Seeded SplitMix64 marker bootstrap produces 95% intervals; refusal floors cover insufficient total, within-European, and founder-subset markers plus low European share.
- Palindromic markers are dropped and counted. Every result discloses usable markers out of 2,621.
- The ASJ-vs-NFE founder subset has 377 markers before LCT-cluster dedup and 375 packaged flags afterward. Its share/interval and supporting likelihood ratio are framed as reference-panel signals, never identity, religion, ethnicity, nationality, or passport status.
AncestryEstimator retains the older 36-AIM seven-population sketch as an explicitly labeled legacy model. The check harness independently reproduces the two primary EM fits and ASJ likelihood ratio in Python.
DNAEngine.swift is the main-actor application model. App.swift supplies workspace commands, inactivity handling, and settings. SwiftUI views are renderers over AnalysisReport, GenomeImportReceipt, and workspace state.
ReportExport.swift emits Markdown, self-contained HTML, JSON, and the PGx research brief. Full reports always include the deduplicated reference appendix and the limitations appendix. Export names combine millisecond time and a UUID fragment; outputs use mode 0600.
OfflinePDFRenderer.swift is the only WebKit rendering path. It uses WKWebsiteDataStore.nonPersistent(), disables content JavaScript and window opening, loads generated HTML with baseURL: nil, permits only the local about: document, rejects external navigation, supports cancellation, and releases the web view after completion. Generated HTML carries default-src 'none' CSP.
AppPaths centralizes persisted paths and enforces 0700 on app-owned directories. LocalEraser.inventory() is throwing so an enumeration failure cannot masquerade as empty.
LocalEraser.eraseAllVerified():
- Enumerates every current child under the app-support directory.
- Attempts every deletion while retaining per-path errors.
- Re-enumerates independently.
- Returns survivors, failures, and
verifiedEmptyonly when both lists are empty.
PrivateWorkspaceStore.eraseEverything() invalidates active generations first. It deletes the Keychain key only after file verification is empty and reports key deletion failure separately. DNAEngine.performVerifiedErase() cancels in-flight work and clears app state before invoking the actor.
Any new persisted file must remain discoverable by live inventory and covered by erase tests. Copies the owner moves outside the app-support directory are deliberately outside the app's deletion authority.
build.sh requires explicit VERSION, BUILD_NUMBER, and exactly one of BUILD_ONLY=1 or INSTALL=1.
Both modes:
- Run
Tools/run_checks.shand isolatedTools/run_vault_checks.shfirst. - Self-test the atomic-swap helper.
- Snapshot hashes of release inputs before compilation and require the same hashes afterward.
- Compile every Swift source with optimization, whole-module optimization, and warnings as errors for arm64 plus x86_64 when the SDK supports it.
- Link only declared Apple frameworks and zlib.
- Sign with hardened runtime using an available local identity or ad-hoc fallback.
- Verify bundle ID, version/build, minimum OS, resources, exact architectures, required links, executable count, and strict code signature.
- Publish
build/DNA Engine.appand a per-file SHA-256 manifest through verified staging.
Build-only mode never touches /Applications. Install mode is separately explicit, refuses a running app, stages and verifies the candidate, creates a verified local backup, atomically swaps the bundle, verifies again, and rolls back on failure.
There is no GitHub, remote release, notarization upload, App Store workflow, website deployment, cloud publication, or sales step.
Tools/run_checks.sh compiles UI-free engines directly for macOS 14 and covers generated-resource lints, safe parser/archive/VCF receipts, determinism, scientific fail-closed expectations, independent ancestry and HWE recomputation, QC fixtures, citation recounting, report language gates, comparison scope, and sandboxed erase.
Tools/run_vault_checks.sh compiles Paths, PrivateVault, and PrivateWorkspaceStore with warnings as errors. It uses a temporary directory and injected keys—never the real Application Support directory or Keychain—to test nonce uniqueness, authentication/tamper/wrong-key failure, no key creation on open, 0700/0600 permissions, verified round trips, migration ordering, generation races, erase, and prevention of post-erase stale writes.
PDF rendering requires a GUI run loop and is manually checked with the synthetic demo during private release QA.
- Add the marker or definition to the correct
Tools/knowledge_sections/*.pymodule. - Add real curation metadata: citation pointer, evidence tier, effect text, transferability, and expected chips.
- Treat palindromic definitions as unresolvable until a separately validated orientation manifest exists; do not bypass the runtime fail-closed rule with a favorable expected allele.
- Add a demo pin only when it is valid and useful for deterministic coverage.
- Run both check suites.
- If expected output changes intentionally, regenerate it only with
UPDATE_EXPECTED=1, then review the complete diff.
Passing lints demonstrates structural completeness and deterministic behavior. It is not a substitute for human scientific review.