Dicklesworthstone/franken_node
Dicklesworthstone/franken_node has 31 GitHub stars and 3 forks. GitHot measured 0 stars in 24 hours and +1 in 7 days. Last checked 2026-11-12 06:12 UTC. Last push 2026-11-04.
Trust-native JavaScript/TypeScript runtime platform built on franken_engine with deterministic compatibility, migration autopilot, extension trust controls, and incident replay.
What you can do
- Trust cards - Per-extension provenance, risk score, audit history, camouflage assessment, revocation state
- Revocation freshness gates - Risky and dangerous actions fail closed when trust state is stale
- Deterministic incident replay - Signed bundles with timeline, evidence, policy decisions; default replay verifies the recorded sequence. Opt-in native capture supports guest re-execution and certified uncaught-exception prefixes, failing closed on mismatch.
- Counterfactual simulator - Re-evaluate the recorded incident decision trace under a different policy mode and inspect the diff
Measured star growth
- in 24h
- 0
- in 7 days
- +1
- in 15 days
- Update delayed
- in 1 month
- Update delayed
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
Originalfranken_node
franken-node is a trust-native JavaScript/TypeScript runtime platform for
extension-heavy systems. It pairs Node/Bun ecosystem velocity with deterministic
security controls, cryptographically-grounded trust operations, and replayable
incident forensics.
# One-line installer (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_node/main/install.sh | bash
[!IMPORTANT] Status: pre-1.0. The CLI surface and the on-the-wire JSON shapes (decision receipts, trust cards, replay verdicts, counterfactual reports, incident bundles) are stable and covered by golden tests. Internal Rust APIs and feature-gated modules may still break between versions. See Stability for the full breakdown.
A concrete scenario
It's Tuesday. A transitive npm dependency in your build was published 14 days ago by a brand-new publisher whose username is 2 characters off a popular library. The package's behavior has slowly drifted in the last three minor releases.
Under your current stack: the typosquat scanner flags it tomorrow; your package-lock pinned the new version yesterday; the egress check runs at deploy time and sees nothing wrong because the malicious payload activates after a 48-hour delay. By Friday you're paging the security team.
Under franken-node:
trust scan --deepreads the package's npm registry record and writes onto its trust card that the package was first published 14 days ago (new packages are raised to at least medium risk), plus a DGIS maintainer-fragility score (sole maintainer, no maintainers, no publish in over a year). A package name one edit away from a pinned popular package is raised to high risk. This happens before the package is admitted. The scan does not see publisher account age (npm does not publish it) or a look-alike publisher username.- Behavior drift across releases is not yet on the card: the BPET
camouflage detector (
security::bpet::camouflage_detector) can flagGradualCreepin an observed-vs-declared capability series, but no runtime path feeds it yet, socamouflage_hintsstays empty. - Under
strict,runrefuses a trusted dependency, and so the dependency never reaches the network, once the revocation frontier is more than 5 minutes old. Underbalancedthe same staleness is a preflight warning (1-hour window). - If the malicious behavior had already executed,
incident bundle+incident replayintegrity-verify the recorded bundle. Runs recorded with--capture-replayalso supportincident replay --executefor native guest execution, a certified uncaught-exception prefix, or a finalized typed engine-stop prefix, using captured host-I/O and admitted process results.incident counterfactual --policy strictscores an alternative policy against the recorded evidence.
Every gate above is a runtime default, not an external scanner. Each run
(including a preflight refusal) and each trust revoke/quarantine/release
and fleet release decision appends a signed, hash-chained entry to the
evidence ledger; other decisions (a trust-scan risk raise, for example) are
recorded on the trust card, not in the ledger.
Who this README is for
| If you're a... | Start at |
|---|---|
| Developer evaluating for a new project | TL;DR → Quick Example → Comparison |
| Operator deploying or running the platform | End-to-End Operator Workflow → Command Reference → Operational Runbooks → Troubleshooting |
| Security auditor verifying claims | Trust-Native Primitives → Threat Model → Cryptographic Primitives → Reproduction Playbook → Verifier SDK |
| Architect making a build-vs-buy decision | Design Philosophy → The Trust Gradient → The Engine-Split Contract → Limitations |
| CI/SRE wiring it into pipelines | Integration Patterns → Structured Logging and Observability → Doctor Walkthrough |
TL;DR
The Problem
Node and Bun made rapid extension development frictionless, but the security and operations layer never caught up. In production, teams stitch together:
- ad-hoc supply-chain scanners that scream and then get ignored
- revocation queries that run after a risky action ships
- incident review sessions that can never reproduce the original execution
- "permission" models that gate file access but not extension behavior
- migration spreadsheets between Node, Bun, and whatever-comes-next
Every layer is best-effort, weakly coupled, and indistinguishable from theater.
The Solution
franken-node makes trust, migration, compatibility, and incident
replay part of the runtime contract, so JS/TS velocity comes with:
- Revocation-first execution: risky actions consult fresh trust state before they run, not after
- Trust cards: every extension carries provenance, behavior risk, audit history, and a camouflage assessment
- Deterministic incident replay: a high-severity incident exports as a signed bundle whose recorded evidence any operator (or the standalone verifier SDK) can integrity-verify and re-derive, and run policy counterfactuals against. Replay re-derives the recording; it does not re-execute the program.
- Migration autopilot: audit → rewrite → validate → rollout, with
unsigned JSON rollback plans (not Ed25519-signed).
migrate validateis static+smoke; behavioral comparison is a separateverify lockstep(Bun+franken dyad by default; real Node.js can be added when available) - Fleet quarantine control plane: quarantine, release, and reconcile
with signed decision receipts. By default over the local durable store
(
transport=file,live_control_plane=false); withfleet serveon a coordinator and[fleet] control_plane_urlon every node, the same commands run as a live multi-node control plane over authenticated HTTP (transport=http,live_control_plane=true) - Verifier SDK: independent third parties can validate receipts,
bundles, and the honesty manifest without trusting the producing
runtime.
bench runreports are SHA256provenance_hashdocuments, not Ed25519-signed SDK attestations.
Why use franken-node?
| Capability | What you get |
|---|---|
| Trust cards | Per-extension provenance, risk score, audit history, camouflage assessment, revocation state |
| Revocation freshness gates | Risky and dangerous actions fail closed when trust state is stale |
| Deterministic incident replay | Signed bundles with timeline, evidence, policy decisions; default replay verifies the recorded sequence. Opt-in native capture supports guest re-execution and certified uncaught-exception prefixes, failing closed on mismatch. |
| Counterfactual simulator | Re-evaluate the recorded incident decision trace under a different policy mode and inspect the diff |
| Migration autopilot | Audit, rewrite, validate, and rollout. --emit-rollback is unsigned JSON (not Ed25519-signed); migrate validate is static+smoke, not verify lockstep |
| Compatibility oracle | Lockstep vs Node (spec) plus Bun as a second reference; matching only Bun stays fail |
| Fleet quarantine control plane | Quarantine, reconcile, release with signed decision receipts; local file transport by default, or a live HTTP coordinator (fleet serve) shared by every node |
| Signed extension registry | Ed25519-signed artifacts, provenance enforcement, assurance levels |
| Remote capability tokens | Scope-bound, single-use-optional Ed25519 capability tokens with audience binding |
| Verifier SDK | Independent verification of receipts, bundles, and the honesty manifest (not Ed25519-signed bench run reports) |
| Operator doctor | Workspace-pressure analyzer, close-condition receipts, evidence-readiness from --input snapshots (not a live broker) |
| Proof pipeline | VEF receipts; proofs queue status and proofs workers restart inspect snapshots / emit restart-request artifacts (not a live broker) |
| Safe-mode lifecycle | Persisted directory-scoped state blocks new application runs; repeated native engine panics enter safe mode automatically, with explicit recovery and retained crash identities (unsigned operator JSON) |
| No unsafe code | #![forbid(unsafe_code)] in both lib.rs and main.rs |
Quick Example
# 1) Install current main (GitHub Latest can lag; see Installation)
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_node/main/install.sh | bash -s -- --method source --node-ref main --engine-ref main
# 2) Bootstrap policy and workspace metadata
franken-node init --profile balanced --scan
# 3) Audit a Node/Bun project for migration risk
franken-node migrate audit ./my-app --format json --out migration-audit.json
# 4) Apply transforms with rollback artifact
franken-node migrate rewrite ./my-app --apply --emit-rollback ./rollback-plan.json
# 5) Compare default Bun+franken dyad (not L1 Node spec)
franken-node verify lockstep ./my-app --runtimes bun,franken-node --emit-fixtures
# 6) Seed trust cards from package.json + lockfile
franken-node trust scan ./my-app --deep --audit
# 7) Run under policy-governed runtime controls
franken-node run ./my-app --policy strict
# 8) Diagnose environment and policy setup
franken-node doctor --verbose --json
# 9) Export and replay a high-severity incident, then run a counterfactual
franken-node incident bundle \
--id INC-2026-0007 \
--evidence-path ./incidents/INC-2026-0007/evidence.v1.json \
--verify
franken-node incident replay --bundle ./INC-2026-0007.fnbundle --trusted-public-key ./keys/replay-trust-anchor.pub
franken-node incident counterfactual \
--bundle ./INC-2026-0007.fnbundle \
--trusted-public-key ./keys/replay-trust-anchor.pub \
--policy strict
# 10) Inspect fleet posture across a zone
franken-node fleet status --zone prod-us-east --verbose --json
First safe workload (current source)
The one-line installer without --method source can install a stale GitHub
Latest tag that cannot run JS. After a source install (or cargo run -p frankenengine-node):
mkdir -p /tmp/fn-first-run && cd /tmp/fn-first-run
franken-node init --profile balanced --json --out-dir .
printf '%s\n' 'console.log("hello-from-engine");' > hello.js
franken-node run ./hello.js --policy balanced
That path is what default_run_executes_fixture_js_through_embedded_engine_without_degraded_fallback
proves. Do not set FRANKEN_NODE_ALLOW_DEGRADED_RUNTIME_FALLBACK. Do not wrap
Node as run.
Charter
See the Product Charter for scope boundaries,
governance model, and decision rules. The charter is the authoritative
reference for what franken_node is, what it is not, and how direction changes
are authorized. The engine-side counterpart lives in
/dp/franken_engine;
this repository is the product layer on top of that substrate.
Design Philosophy
- Compatibility is a wedge, not the destination. franken-node chases practical Node/Bun migration first, then pushes beyond the baseline runtimes with trust-native behavior that incumbent runtimes cannot retrofit.
- Security controls are operational, not decorative. Policy gates, revocation checks, and quarantine paths are runtime defaults with measurable behavior, not advisory dashboards.
- Claims require evidence. Benchmark, resilience, and security statements map to reproducible artifacts and a verifier SDK that runs outside the runtime that produced them.
- Determinism drives incident quality. Replay and forensics depend on stable event ordering, stable schemas, and explicit control contracts. Schema versions are pinned and registered.
- Performance optimization preserves semantics. Hot-path work is accepted only with conformance evidence and bounded tail-latency impact. Benchmarks live alongside the code they validate.
Comparison
| Area | franken-node | Node.js | Bun | Deno |
|---|---|---|---|---|
| Per-extension trust cards | Built-in | External tooling | External tooling | External tooling |
| Revocation-aware execution gates | Built-in | Not native | Not native | Not native |
| Deterministic incident replay bundles | Built-in | Not native | Not native | Not native |
| Counterfactual policy simulation | Built-in | N/A | N/A | N/A |
| Compatibility divergence receipts | Built-in | N/A | N/A | N/A |
| Migration autopilot pipeline | Built-in | External scripts | External scripts | External scripts |
| Fleet quarantine log (file transport) | Built-in local | External platform | External platform | External platform |
| Signed extension registry | Built-in | npm (signature optional) | bunx (no enforcement) | deno.land (URL trust only) |
| Threshold-signature verification | Built-in | Not native | Not native | Not native |
| Verifier SDK for public claims | Built-in | N/A | N/A | N/A |
| Operator doctor + workspace pressure | Built-in | N/A | N/A | N/A |
| Permission model | Policy profiles + trust gates | None | None | Permission flags |
| Runtime memory safety | forbid(unsafe_code) Rust |
C++ engine | Zig/JSC | Rust + V8 |
Installation
[!IMPORTANT] This repository depends on sibling engine crates from
franken_enginevia the Engine Split Contract. Source builds require both repositories checked out side-by-side. The one-line installer side-steps this by shipping prebuilt binaries.
Option 1: One-line installer (recommended)
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_node/main/install.sh | bash
Windows (PowerShell):
irm https://raw.githubusercontent.com/Dicklesworthstone/franken_node/main/install.ps1 | iex
The installer tries the latest GitHub release tag first. As of 2026-10-21,
Latest is v0.2.0,
published 2026-10-04 with Linux x86_64, macOS arm64 and Windows x86_64 assets.
Its macOS binary executes native guest JavaScript, but it predates newer
commands such as run --capture-replay and fleet serve. A release tag can lag
main substantially. For current source behavior:
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_node/main/install.sh | bash -s -- --method source --node-ref main --engine-ref main
A checksum-verified release asset is used when you pass --method release or
--version vX.Y.Z. Asset availability does not establish that the release
implements every current README surface. Check the installed --version and
command help before relying on newer behavior. Pass --help (bash) / -EasyMode
(PowerShell adds the dir to PATH) for options.
Child-process spawning is not enabled by any runtime profile. Profile-governed
run also rejects external Node/Bun runtimes because they cannot enforce the
engine capability contract. Profile-governed execution requires a build with
the embedded engine feature; an arbitrary external --engine-bin is never
trusted by name alone. Use the explicit lockstep or compatibility-corpus tools
for comparative execution. On Linux, an operator who intends to opt in may
pass --enable-process-spawn; the installer
then requires the installed binary's bounded Bubblewrap readiness probe to
pass. The installer does not install or configure Bubblewrap. Install the
distribution package first (for example, sudo apt install bubblewrap), then
verify it explicitly:
franken-node doctor process-spawn-readiness --json
macOS and Windows fail closed for this opt-in because the current containment contract requires Linux PID and user namespaces. Normal installations do not probe Bubblewrap and are unaffected.
[!NOTE] Homebrew is not currently published for
franken-node; the publicDicklesworthstone/homebrew-taprepository does not yet ship afranken-nodeformula. Use the installer above or build from source.
Option 2: Build from source (split-repo layout required)
# Start in a fresh parent directory; the bootstrap preserves existing checkouts.
git clone https://github.com/Dicklesworthstone/franken_node.git
cd franken_node
bash scripts/bootstrap_source.sh --workspace "$PWD" --engine-ref main \
--receipt ../franken-node-source-inputs.tsv
cargo build --release -p frankenengine-node
./target/release/franken-node --version
Expected local layout:
<parent>/
franken_engine/
franken_node/
frankentui/
fastapi_rust/
sqlmodel_rust/
The bootstrap materializes all four sibling path dependencies. Supporting
revisions come from packaging/source-siblings.tsv; --engine-ref selects the
engine and the new receipt records its resolved commit. Supply an immutable
engine commit for a reproducible source selection. Existing siblings must be
clean and match the requested commits; the bootstrap refuses to reset or replace
them. The receipt records source inputs, not a successful build or qualification.
The Cargo workspace package is named frankenengine-node; the produced
binary is named franken-node. rust-toolchain.toml selects
nightly-2026-09-21 with clippy and rustfmt. The current published
fsqlite-pager 0.4.1 dependency requires nightly intrinsics on x86_64;
Rust 1.99 stable fails with E0554. This is a compiler requirement, not a
completed source-build qualification. The workspace uses Rust 2024 and
forbids unsafe code.
Option 3: Verify a release archive
franken-node verify release ./release-dir --key-dir ./trusted-public-keys
[!WARNING]
verify releaseis fail-closed and accepts no built-in trust roots. You must point--key-dirat a directory of public keys you trust; without it, verification refuses to proceed.
Quick Start
- Create a default config:
franken-node init --profile balanced --scan - Audit an existing project:
franken-node migrate audit ./my-app --format json --out migration-audit.json - Seed trust cards from your dependency graph, then record a fresh
revocation frontier:
franken-node trust scan ./my-app --deep --audit franken-node trust sync --force--deep/--auditandtrust syncfetch registry and OSV data through a scoped remote capability.initprovisions it: a signing key at.franken-node/keys/remotecap-signing.key(mode 0600) and a 90-day token at.franken-node/remotecap/trust-scan-token.jsonwhose only scope is network egress toregistry.npmjs.org,api.deps.devand the OSV query endpoint. Re-runninginitrenews an expired token;remotecap revoke --token-filerevokes it.FRANKEN_NODE_REMOTECAP_KEYandFRANKEN_NODE_TRUST_SCAN_REMOTECAP_TOKENoverride both. Without a token the scan fails closed. Understrict,runadmits a tracked dependency only if the recorded frontier is under 5 minutes old;balancedwarns once it is over an hour old or missing. - Compare the default Bun+franken dyad (not the L1 Node spec):
Matching only Bun stays fail. Corpus L1 requires real Node as spec.franken-node verify lockstep ./my-app --runtimes bun,franken-node - Run in policy-governed mode:
franken-node run ./my-app --policy strict - Inspect trust and fleet state:
franken-node trust list --risk high franken-node fleet status --json # local store, or the live coordinator when configured franken-node incident list --severity high - Diagnose environment health:
franken-node doctor --verbose franken-node doctor workspace-pressure
Use Cases
| Scenario | Why franken-node fits |
|---|---|
| Regulated SaaS deploying third-party extensions | Trust cards + revocation freshness gates + signed registry give a continuous audit trail of which artifacts ran at which version with what attestations. The verifier SDK lets external auditors validate claims without trusting the operator. |
| Multi-region operator with quarantine SLA | One node runs franken-node fleet serve; every other node points [fleet] control_plane_url at it. trust quarantine on any node reaches every agent over authenticated HTTP, heartbeats are stamped on the coordinator's clock, and fleet reconcile waits on real multi-node convergence. Without a coordinator the same commands use the local file transport. |
| Migration from legacy Node to a hardened runtime | migrate audit → migrate rewrite --apply --emit-rollback → verify lockstep is an end-to-end pipeline. --emit-rollback writes unsigned JSON (not Ed25519-signed); migrate validate is static+smoke, not lockstep. verify lockstep is the behavioral oracle. |
| Post-incident counterfactual analysis | incident bundle --verify exports a signed, deterministic snapshot of the incident window. incident counterfactual --policy strict answers "would tighter policy have blocked this?" with a reproducible diff. |
| Public claim verification | frankenengine-verifier-sdk runs outside the producing runtime. Third parties verify receipts, capsules, and counterfactuals without trusting franken-node's own logs. |
| Operator triage under disk / build pressure | doctor workspace-pressure analyzes disk, memory, RCH queue depth, and build-fleet state, routes through balanced/conservative/permissive policy, and emits recommended actions. |
| Long-running fleet agent on edge nodes | fleet agent --zone <z> --poll-interval-secs <n> pulls actions from the coordinator (or the local file-transport log when none is configured), applies them to the node's own trust-card registry with bounded retries, and pushes a heartbeat each cycle. |
| Compliance archival | The evidence ledger is append-only, signature-chained, and witness-traced. Incident bundles plus the registry's signed publish records produce a defensible chain of custody. |
End-to-End Operator Workflow
Concrete narrative: a platform team taking a moderately-sized Node application through adoption, rollout, and a real incident.
Day 0 — Adoption
# Bootstrap policy + workspace metadata + dependency trust scan
franken-node init --profile balanced --scan
This writes franken_node.toml, creates .franken-node/state/ for durable
state, and runs a baseline trust scan against package.json. The scan
populates trust cards keyed by npm:<scope>/<name>@<version> with publisher
identity, publication date, dependent counts, and any OSV vulnerability
hits.
Day 1 — Migration assessment
franken-node migrate audit ./app --format json --out audit.json
franken-node migrate-report ./app --format html --output report.html
migrate audit produces a SARIF-compatible findings file plus a
deterministic JSON inventory. migrate-report produces a single-document
operator-facing assessment suitable for review by stakeholders outside the
runtime team.
Day 2 — Apply transforms, validate
franken-node migrate rewrite ./app --apply --emit-rollback rollback.json
franken-node verify lockstep ./app --runtimes bun,franken-node --emit-fixtures
--emit-rollback writes an unsigned JSON MigrationRollbackPlan
(original/rewritten file contents; not Ed25519-signed). Restore from that
plan and/or .migrate-backup/ snapshots. verify lockstep defaults
to the Bun+franken dyad and records divergence receipts; that dyad is not
the L1 spec. Matching only Bun stays fail. On hosts with real Node.js, pass
--runtimes node,bun,franken-node — Node is then the spec. Material
behavioral deltas fail closed.
Day 5 — Pre-production validation
franken-node verify recovery-runbook --readiness-input broker-snapshot.json --json
franken-node doctor --verbose --json
franken-node ops validation-readiness --input broker-snapshot.json --json
ops validation-readiness and verify recovery-runbook inspect
--input / --readiness-input snapshots (live_broker=false); they
do not start a live broker. If any recorded precondition is stale, the
recovery runbook names which gates need to be re-run.
Day 7 — Production rollout
franken-node run ./app --policy balanced
Run franken-node verify lockstep ./app --runtimes bun,franken-node as a
separate comparative dyad (not the L1 Node spec; matching only Bun stays
fail) before profile-governed execution. run never launches an external
reference runtime.
Day 21 — Incident
A high-severity alert fires; a transitive npm dependency was revoked upstream due to a typosquatting attack. The revocation freshness gate catches it on the next risky action and quarantines the node.
# Containment
franken-node fleet status --json --verbose
franken-node trust quarantine --artifact sha256:abc...
# Forensics
franken-node incident list --severity high
franken-node incident bundle --id INC-2026-0042 --verify
franken-node incident replay --bundle INC-2026-0042.fnbundle \
--trusted-public-key keys/replay-trust-anchor.pub
# Counterfactual: would strict mode have blocked this?
franken-node incident counterfactual --bundle INC-2026-0042.fnbundle \
--trusted-public-key keys/replay-trust-anchor.pub --policy strict
The incident bundle is a self-contained, deterministic snapshot: timeline, environment, policy decisions, evidence-ledger entries, and the trust artifacts referenced during the incident. The counterfactual report includes a diff of decisions, actions taken vs. blocked, and any newly emitted evidence.
Re-execute captured native JavaScript
Opt in when recording a run to retain its entry source, execution settings, arguments, and raw host-I/O and admitted process results in the signed run record. The capture is limited to 4 MiB and may contain application secrets.
franken-node init --profile balanced --out-dir .
franken-node run app.js --capture-replay --json > run.json
# Read receipt.receipt_id from run.json, including when the guest threw.
franken-node incident capture --from-run RUN_RECEIPT_ID --json
# Use incident_id from that capture result.
franken-node incident bundle --id INCIDENT_ID --verify
franken-node incident replay --bundle INCIDENT_ID.fnbundle --execute \
--trusted-public-key .franken-node/keys/receipt-signing.pub --json
Replay authenticates the bundle before executing its captured source through the native engine. It consumes the exact recorded host requests and responses; it does not repeat filesystem writes, network requests, or child processes. The original source and input files may be missing or changed. An unused transcript suffix, an unexpected request, or a different result fails verification.
Run-derived exports also carry the exact original signed run record. An exporter can sign the incident with a different key without replacing that original authority. To require both signatures and bind the incident's receipt, effect ledger, native capture and outcome to the original statement:
franken-node incident replay --bundle INCIDENT_ID.fnbundle --execute \
--trusted-public-key keys/bundle-exporter.pub \
--trusted-run-public-key keys/original-run-authority.pub --json
The original run key is supplied independently; a key embedded in the bundle
cannot establish trust. A missing proof, wrong original signer or inconsistent
run binding fails before guest execution. Successful JSON reports
run_origin_verified: true and the authenticated receipt identity. The same
portable verification is available in the verifier SDK and its Python,
JavaScript and TypeScript bindings, without the
original project or private keys. This verifies the original receipt authority's
statement; independent engine-session certification and guest re-execution
remain separate checks. Historical managed captures require their original
authenticated run record when first exported with this proof.
| Replay mode | Verified result |
|---|---|
Default, replay_kind: recorded_trace |
Signature, bundle integrity, and the recorded decision sequence. |
--execute, verification_scope: guest_execution |
Fresh guest output, value, IR3/IR4 witness, instruction count, nondeterminism trace, lane, exit code, host-I/O transcript, and the global effect journal when process authority was admitted. |
--execute, verification_scope: guest_failure_prefix |
The same uncaught exception, console output, execution trace identity, finalized host-I/O prefix, and any captured global process/I/O journal. Completed-witness, instruction-count, nondeterminism, lane, exit-code, and policy comparisons remain null. |
A captured exception leaves the original run failed with exit status 1 and
its original error and output. A later replay exits successfully when it
reproduces that failure. Runs without host effects receive a completion or
failure timeline event, without inventing an effect receipt.
Strict and balanced runs with a valid signed child-process admission can now capture process replay. The worker retains bounded exact request preparation, including policy-owned executable and shell aliases, plus the global ordering of filesystem, network, and process outcomes. Replay uses those captured inputs with an inert process provider and an expired process authority; it does not reopen executables, consult the current process policy, or launch a child. An unknown preparation, conflicting capture, reordered effect, or unconsumed journal suffix cannot produce a successful replay verdict, including when the guest catches the immediate error. A request refused before journal admission may lack replayable preparation and consequently fail replay verification.
Process capture uses the engine's conservative Unknown exception provenance
in the original execution as well as replay. This can restrict flows that an
ordinary run would allow; it keeps the actual original and replay witnesses
comparable without making replay data more trusted or waiving any comparison.
Preparation is limited to 4,096
unique entries and 2 MiB within the overall 4 MiB capture limit.
Entry-only captures disable runtime module loading during replay. Source now
implements --frozen-modules --capture-replay: it retains the existing bounded
source capsule, graph pins and original prepared entry binding. Replay restores
only that captured resolver, retaining the original capability restrictions
without reading or extracting the original project. Native qualification of
this new multi-file path is still pending; see the
execution TODO.
The capture envelope is now franken-node/native-replay-capture/v3; historical v2 captures
are refused rather than treated as authenticating newly added fields.
Ambient environment authority, timeouts, interrupted sessions, and uncertified
engine failures remain outside this replay scope. For completed runs,
decisions_match remains informational. Without frozen sources, withholding
module authority changes the capability population used by Bayesian policy
evaluation; neither path's guest verdict certifies original policy decisions.
--execute requires a build with the engine feature.
Day 22 — Release
franken-node fleet release --incident INC-2026-0042 --json
fleet release lifts quarantine with a signed receipt and waits for
convergence of the configured fleet store: the local file transport
(live_control_plane=false), or every node heartbeating into a fleet serve coordinator (live_control_plane=true). franken-node fleet reconcile confirms that catch-up; franken-node fleet status --verbose
shows the per-node application of the release.
Quarterly — Public claim verification
# Independent third party, with their own trust anchors
franken-node verify release ./release-dir --key-dir ./auditor-trust-roots
franken-node bench run --scenario secure-extension-heavy --output bench.json
Auditors use frankenengine-verifier-sdk to recompute receipts and verify
capsule integrity without trusting the runtime that produced them. A
bench run report is a SHA256 provenance_hash document, not an
Ed25519-signed SDK attestation; the SDK does not independently attest
benchmark claims.
Stability
franken-node is pre-1.0. The contract surface is split into three
stability bands:
| Surface | Stability | Notes |
|---|---|---|
CLI command shape (franken-node … subcommands, flags, exit codes) |
Stable | Removals and breaking flag changes land as a deliberate deprecation with a CHANGELOG entry. Downstream tests assert this surface in cli_subcommand_goldens.rs. |
| On-the-wire JSON shapes (decision receipts, trust cards, replay verdicts, counterfactual reports, incident bundles, structured-log events) | Stable | Every shape has a schema_version and goldens under tests/golden/. Schema bumps are explicit and registered in schema_versions.rs. |
Internal Rust APIs (everything under crates/franken-node/src/) |
In flux | Module names, struct fields, and feature-gated surfaces may change between versions. Pin a specific commit when depending. |
External verifier SDK (frankenengine-verifier-sdk at sdk/verifier/) |
Stabilizing | Smaller, intentionally narrower than the product crate; intended to be the long-lived audit interface. |
Anything not yet documented here, expect movement. The headline claims ("trust-native runtime", "deterministic replay", "fail-closed gates") are backed by code today; the goal of pre-1.0 is to finalize ergonomics and dependency hygiene, not to retract the security commitments.
Runtime Profiles
--profile <name> (or FRANKEN_NODE_PROFILE) selects the runtime policy
profile, not the packaging profile:
| Profile | When to use | Behavior |
|---|---|---|
strict |
High-assurance environments, regulated workloads, post-incident lockdowns | Maximum trust-card freshness requirements, revocation gates on all risky actions, all divergence emits receipts, migration.require_lockstep_validation=true (doctor WARN when false; migrate validate still does not run verify lockstep) |
balanced |
Default for most teams | Reasonable freshness windows, revocation gates on risky and dangerous classes, lockstep recommended as a separate verify lockstep stage |
legacy-risky |
Constrained migration windows on legacy codebases | Permits insecure compatibility behaviors when explicitly enabled by policy. Not a long-term mode. |
Native execution also applies the following independent resource limits:
| Profile | Instructions per run |
Source bytes per module | Tokens per module | Parser recursion depth |
|---|---|---|---|---|
strict |
200,000,000 | 256,000 | 32,768 | 128 |
balanced |
1,000,000,000 | 1,048,576 | 65,536 | 256 |
legacy-risky |
5,000,000,000 | 2,097,152 | 131,072 | 384 |
runtime.max_instructions replaces the instruction budget. The optional
runtime.max_parse_source_bytes and runtime.max_parse_tokens settings replace
their respective parser limits for the entry file and every required/imported
JavaScript module. Source bytes and tokens are separate limits: a file can fit
within the byte limit and still exceed the token limit. Recursion depth remains
profile-governed, and the wall-clock timeout still applies.
For a large npm dependency such as lodash, an operator can keep balanced and
explicitly set max_parse_tokens = 131_072. This admits additional parser work
without changing the profile's trust requirements or other resource limits.
The allowed override ranges are 1–2,097,152 source bytes and 1–131,072
tokens; zero, malformed values, and values above these caps fail configuration
validation. Overrides never silently clamp. The effective source, token, and
recursion limits are included in native run receipts and engine decisions.
The optional runtime.max_heap_objects, runtime.max_total_memory_bytes, and
runtime.max_console_entries settings also apply to both native lanes. Each
must be a positive integer; environment values override TOML and invalid values
fail before guest execution. Leaving an option unset preserves the engine's
existing lane default:
| Native lane | Heap-object allocations | Accounted memory | Console entries | Console bytes |
|---|---|---|---|---|
| Deterministic | 100,000 | 64 MiB | 100,000 | 8 MiB |
| Throughput | 1,000,000 | 512 MiB | 1,000,000 | 8 MiB |
Heap objects count allocations until the engine supports live-object
reclamation. Accounted memory excludes console text and is not a process-RSS
ceiling. The console entry and byte ceilings apply independently; overflow
fails the run while retaining earlier output, and the byte ceiling remains
fixed. Completed native run receipts and engine decisions include
execution_limits with the effective limits for both lanes and selected_lane
identifying the lane actually used.
Function-frame capacity can be increased without selecting a different trust
profile. Set runtime.max_registers to a value in 1–65,536 to replace the
register window on both native lanes, or runtime.max_call_depth in
1–10,000 to replace the guest call-depth ceiling. These are independent of
the instruction, parser, memory, and console budgets; exceeding any active
limit still fails execution. Invalid overrides are rejected before execution.
Register backing storage is bounded separately by the engine and is outside
the reported heap-memory ceiling; a larger register window can substantially
increase process memory. The engine's independent native-stack guard also
continues to apply.
Leaving them unset preserves the profile defaults:
| Profile | Deterministic registers per frame | Throughput registers per frame | Guest call depth |
|---|---|---|---|
strict |
128 | 256 | 32 |
balanced |
256 | 4,096 | Engine default |
legacy-risky |
8,192 | 16,384 | 128 |
For a function that needs more than balanced's 256 registers, an explicit
max_registers = 2_048 gives it a larger frame while retaining balanced's
capability and trust rules. Effective register and call-depth ceilings are
recorded for each lane under execution_limits. Historical receipts that did
not record those two fields leave them absent; replay does not invent values.
No profile—including legacy-risky—grants process_spawn. That capability is
impossible by default and requires an expiring Ed25519-signed
ChildProcessSpawn token bound to the complete resolved policy/config plus a
validated Linux Bubblewrap backend. Missing, malformed, expired, or
policy-mismatched grants fail before guest execution. Profile-governed run
always rejects external Node/Bun paths because they cannot claim this
containment contract. The signer is anchored outside project configuration at
the fixed, root-owned /etc/franken-node/process-spawn-trust-anchor.pub as exactly 64
lowercase hexadecimal Ed25519 public-key characters; the file and every path
ancestor must be non-writable by group/other, so a project cannot declare its
own trusted signer.
Packaging profiles (local, dev, enterprise) live in
packaging/profiles.toml and govern installer
metadata, not runtime behavior.
Runtime Lane Scheduler
The runtime separates concurrent work into scheduler lanes so that hot-path work cannot starve control-critical work and so that bulk maintenance cannot push real-time deadlines past their SLO. There are two related layers:
The four logical lanes (runtime::lane_scheduler::SchedulerLane):
| Lane | Carries | Stable label |
|---|---|---|
ControlCritical |
Epoch transitions, barrier coordination, marker writes | control_critical |
RemoteEffect |
Remote computation invocations, artifact uploads | remote_effect |
Maintenance |
Garbage collection, compaction, cleanup tasks | maintenance |
Background |
Telemetry export, log rotation, low-priority housekeeping | background |
Each lane has a built-in priority order (top to bottom above) so that
ControlCritical work always wins arbitration against Background work
when both are runnable.
Named lane configs ([runtime.lanes.<name>] in franken_node.toml)
parameterize the concrete bulkhead each task class is routed through.
Common configurations:
[runtime.lanes.cancel]
max_concurrent = 12
priority_weight = 100
queue_limit = 24
enqueue_timeout_ms = 25
overflow_policy = "reject"
[runtime.lanes.realtime]
max_concurrent = 24
priority_weight = 60
queue_limit = 48
enqueue_timeout_ms = 75
overflow_policy = "enqueue-with-timeout"
Task classes are stable identifiers (runtime::lane_scheduler::TaskClass)
that the runtime maps to lanes via mapping rules. Built-in classes
include epoch_transition, barrier_coordination, marker_write,
remote_computation, artifact_upload, artifact_eviction,
garbage_collection, compaction, telemetry_export, and
log_rotation. Operators can inspect or exercise lane behavior with:
franken-node runtime lane status --json
franken-node runtime lane assign epoch_transition --trace-id ops-poke
The lane assignment path is deterministic: the same task class with the same timestamp routes to the same lane. The lockstep oracle and replay engine depend on that property to work across runs.
Network Egress and SSRF Policy
security::ssrf_policy and security::network_guard together enforce
egress policy as a runtime default. Every guest network call on the
run path transits the SSRF policy gate before any socket opens. run
executes guests only in the native engine; external node/bun runtimes are
refused under the governed profiles.
The policy is configured in franken_node.toml:
[security.network_policy]
ssrf_enforcement = "block" # none | monitor | block
Mechanics:
- CIDR awareness.
CidrRangerepresents IPv4 CIDR blocks; the gate rejects connections to any IP that falls inside a deny CIDR before the socket opens.is_private_ipblocks the standard private ranges (10/8,172.16/12,192.168/16) and loopback by default unless an explicit allowlist entry permits them. - Allowlist entries.
AllowlistEntryitems pin a hostname (or CIDR), optional port, and the connector ID that may use it. The allowlist is scoped per connector so a misbehaving extension cannot piggyback on another extension's permissions. - DNS rebinding defense.
check_ssrf_resolved_ipsre-validates every resolved IP from a hostname after DNS resolution; a hostname that allow-listed against1.2.3.4but suddenly resolves to127.0.0.1is rejected. - Policy receipts. Each enforcement decision can emit a
PolicyReceiptand aSsrfAuditRecordthat lands in the evidence ledger so post-mortems can reconstruct exactly which endpoint was reached, denied, or rebound. - Capability binding.
franken-node remotecap issue --endpoint <e>pins capability tokens to specific endpoints; the gate refuses use of a token at any other endpoint regardless of the operation requested. - Enforcement modes.
ssrf_enforcementisblock(default),monitorornone. On the nativerunpathmonitorcurrently enforces exactly likeblock; an allow-and-record mode is not implemented yet. Onlynonedisables the gate. - TLS for guest
httpsegress. A guesthttps.*/fetch(https://…)call performs a real TLS handshake in the engine's network mechanism (rustls; certificate verification against the built-in webpki roots).tls_extra_roots_pem_pathunder[security.network_policy]ADDS operator trust anchors (private CAs) to the webpki roots — it never replaces them, and a missing or unparseable bundle fails closed (certificate verification simply fails for hosts signed by those anchors; verification is never weakened or skipped).
Network allowlist parsing is fuzz-covered
(fuzz_targets/network_allowlist_entry_*) and the rebind path is
exercised by the connector interop suites.
Command Reference
franken-node --help prints the full surface. The following tables list
every leaf command available in the current build.
Core lifecycle
| Command | Purpose |
|---|---|
franken-node init |
Bootstrap config, policy profile, and .franken-node/state/ workspace metadata. Flags: --profile, --config, --out-dir, --overwrite, --backup-existing, --scan, --state-dir, --no-state, --json (franken-node/init-cli/v1; failures franken-node/init-error-cli/v1). |
franken-node run <app_path> |
Run app under policy-governed runtime controls. A directory runs its entry file as node <dir> does: package.json main (as given, with .js, or its index.js), else index.js; a main outside the directory is refused. The app path is handler-required so --json failures emit franken-node/run-error-cli/v1 instead of a human clap error. Flags: --policy, --config, --runtime (auto|node|bun|franken-engine), --engine-bin, --compat-preflight, --json (early failures franken-node/run-error-cli/v1; blocked preflight prints JSON then exits 1). External Node/Bun selections fail closed; use verify lockstep for comparison. |
franken-node doctor |
Local diagnostics: config/profile, close-condition, evidence snapshot, workspace pressure, bwrap. Exits 1 when the overall verdict is fail (after printing the full report) and 0 for pass/warn. Workspace pressure (host load, builds, disk) is advisory in this command and never fails it; doctor workspace-pressure gives the full signal. --policy-activation-input is a snapshot JSON file (not a live guardrail probe). Flags: --config, --profile, --policy-activation-input, --verbose, --json (early failures franken-node/doctor-error-cli/v1), --structured-logs-jsonl. |
Migration
| Command | Purpose |
|---|---|
franken-node migrate audit <path> |
Inventory migration risk. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --format (json|text|sarif), --json (alias for --format json), --out. |
franken-node migrate rewrite <path> |
Apply migration transforms. --emit-rollback writes an unsigned JSON MigrationRollbackPlan (not Ed25519-signed) plus .migrate-backup/ snapshots. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --apply, --emit-rollback, --json. |
franken-node migrate rollback <path> |
Inspect retained native rewrite transactions or restore one by its explicit identity. --transaction <ID> selects the transaction; without --apply the command is preview-only, and there is no latest-ID inference. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --transaction, --apply, --json. |
franken-node migrate validate <path> |
Rerun static migration audit checks and, unless --static-only, a transformed-runtime smoke test. Does not run verify lockstep. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --format (json|text), --json, --static-only. |
franken-node migrate rollout <path> |
Drive the progressive rollout state machine (shadow → canary → ramp → default) with fail-closed gates. --action is promote (default), rollback, or status; --stage overrides the target stage; --ramp-pct applies in ramp stage. The state machine records stages and evidence; it does not start or route traffic to instances. Leaving Shadow requires --lockstep-report <path>, the JSON printed by franken-node verify lockstep <project> --json. The report must have verdict Pass, include a franken leg and a reference leg, and match the project's current input. Without it the promotion fails closed. --force bypasses the confidence and evidence checks but never records lockstep_verified. --no-auto-rollback disables automatic rollback on verification failure. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --migration-id, --action, --stage, --ramp-pct, --lockstep-report, --force, --no-auto-rollback, --json. |
franken-node migrate-report <path> |
Export one-command migration assessment. The project path is handler-required so --json failures emit franken-node/migrate-error-cli/v1 instead of a human clap error. Flags: --format (json|html), --json (alias for --format json), --output. |
migrate rewrite <path> --apply --verify checks captured dependency metadata
and installed package identities before differential execution. Its npm 11
compatibility path gives npm-shrinkwrap.json precedence over package-lock.json,
including when the ignored lockfile is stale or malformed. Root scalar
overrides can select supported registry ranges and replacement forks such as
"vulnerable": "npm:@maintained/fork@^2", including $dependency references to
aliased direct dependencies. The replacement's name and range are checked at the
original dependency's installation location in both the lockfile and actual
installed tree; raw declarations remain unchanged. Direct dependency conflicts
retain npm's exact-spec EOVERRIDE behavior. Nested or version-qualified rules,
nonregistry sources, omitted alias ranges, dist-tags, and bundled or
nested-shrinkwrap contexts require review.
Workspace and installed-package manifests cannot redefine the root's overrides.
On Linux, trust dependency queries use that same captured override authority,
including the replacement fork's identity and its locked version. Reports keep
the original dependency request alongside the effective override. The topology
commitment binds the complete selected root rules, including unused entries and
resolved $dependency references; later queries do not reopen package metadata.
Unsupported override contexts and conflicting captured identities fail closed.
Bun text-lock projects also support root scalar registry overrides and
resolutions, checked against both the lock graph and actual hoisted or isolated
installations. The presence of overrides, including an empty object, takes
precedence over resolutions. Every selected rule must match bun.lock.overrides,
including unused rules; original dependency declarations and input hashes remain
unchanged. Bun permits direct dependency replacement and treats literal * as an
active replacement. Versioned npm: aliases keep their original requirements.
Nested or version-qualified rules, reference/catalog replacements, replacement
sources and contextual workspace replacements still require review. Lifecycle,
native-addon, patch and trusted-dependency checks remain in force.
The shared Unix workspace selector accepts bounded brace alternatives such as
{apps,packages}/*, packages/{core,{cli,api}} and packages/{,group/}core,
alongside literal paths, * and standalone **. Alternatives and overlapping
selectors share one descriptor-relative directory walk, and each manifest is
captured once. Checked migration requires a match for each authored selector,
rather than for every alternative. Expanded alternatives count against the
existing pattern limit. Numeric brace ranges, negation and other unsupported
glob dialects still require review; symlink and reserved-directory restrictions
remain in force.
Verification
| Command | Purpose |
|---|---|
franken-node verify module <id> |
Verify a franken_node crate-src module contract (api, cli, …). Not guest JS conformance. The module id is handler-required so --json failures emit verifier-cli-contract-v1 instead of a human clap error. Flags: --json. |
franken-node verify source-graph <project> <entrypoint> |
Capture the existing bounded static module graph without executing guest code. Emits its content commitment for later pinned execution; does not authenticate a publisher or grant authority. Flags: --json (existing franken-node/module-source-graph/v2 report), --allow-contained-symlinks. Exit 0 means the declared static scope is complete; incomplete capture or errors exit 1. Missing/unsafe input errors use franken-node/verify-source-graph-error-cli/v1. Unix only. |
franken-node verify migration <id> |
Check a local migration evidence record and its post-conditions; does not re-run migrate rewrite. The migration id is handler-required so --json failures emit verifier-cli-contract-v1 instead of a human clap error. Flags: --json (live_rewrite=false, source=local_evidence_record). |
franken-node verify compatibility <target> |
Probe a concrete runtime (node, bun, franken-node). Profile names (strict, balanced, legacy-risky) fail closed and do not auto-PASS. The target is handler-required so --json failures emit verifier-cli-contract-v1 instead of a human clap error. node/bun smoke is console.log; franken-node smoke is fixture JS through the embedded engine (run --console-only), not --version alone. Flags: --json. |
franken-node verify corpus <path> |
Verify corpus schema and coverage. The corpus path is handler-required so --json failures emit verifier-cli-contract-v1 instead of a human clap error. Flags: --json. |
franken-node verify lockstep <path> |
Compare runtimes in lockstep. Default --runtimes bun,franken-node. Use --runtimes node,bun,franken-node only when node is real Node.js; Node is then the spec and matching only Bun stays fail. The project path is handler-required so --json failures emit franken-node/verify-lockstep-error-cli/v1 instead of a human clap error. --emit-fixtures writes divergence fixtures. Flags: --json (stderr banner off; early failures franken-node/verify-lockstep-error-cli/v1). |
franken-node verify release <path> |
Verify release artifact signatures. The release path and --key-dir are handler-required so --json failures emit franken-node/verify-release-error-cli/v1 instead of a human clap error. Fails closed without --key-dir. Flags: --json (franken-node/verify-release-cli/v1; early failures franken-node/verify-release-error-cli/v1). |
franken-node verify transparency-log <path> |
Verify a complete retained chain from a JSONL export or .franken-node/state/evidence-ledger.db. --public-key verifies contents and predecessor-bound signatures; legacy standalone signatures leave chain history unproven with nonzero exit. --expected-head-hash <64 lowercase hex> additionally checks a trusted, separately retained checkpoint and requires --public-key. Empty or oversized logs fail closed. JSON: franken-node/verify-transparency-log-cli/v1; handler failures: franken-node/verify-transparency-log-error-cli/v1. |
franken-node verify recovery-runbook |
Generate a recovery runbook from a --readiness-input snapshot (not a live broker). Flags: --json (early failures franken-node/verify-recovery-runbook-error-cli/v1). |
Trust and supply chain
| Command | Purpose |
|---|---|
franken-node trust card <id> |
Show trust profile for one extension. The extension id is handler-required so --json failures emit franken-node/trust-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node trust list |
List extensions; filter by --risk, --revoked. --json emits the card list as JSON. |
franken-node trust scan [path] |
Populate baseline trust cards from package.json. A plain rescan skips existing cards; with --deep/--audit it refreshes them from the fetched evidence (same version only; lowering risk needs an OSV answer from the default endpoint) and keeps certification, revocation, quarantine and camouflage marks. Flags: --deep, --audit, --json. |
franken-node trust observe <observation.json> --collector-key <public-key-file> |
Verify collector-attested measurements for an exact package artifact, append a durable observation stream, and apply BPET camouflage findings to its signed trust card. Repeated observations are idempotent. --json emits franken-node/behavioral-ingestion/v1. See the measurement and signing contract. |
franken-node trust sync |
Refresh trust-card cache and npm vulnerability state from OSV; --force to ignore caches. --json emits franken-node/trust-sync-cli/v1. |
franken-node trust revoke <id> |
Revoke an artifact or publisher in the local trust-card registry (not a live fleet). The extension id is handler-required so --json failures emit franken-node/trust-error-cli/v1 instead of a human clap error. Optional --receipt-signing-key, --receipt-out. --json emits the revoked trust card; failures franken-node/trust-error-cli/v1. |
franken-node trust quarantine |
Quarantine a suspicious artifact in the local trust-card registry and publish the quarantine to the fleet store: the local file transport, or the live coordinator when [fleet] control_plane_url is set (every agent polling it applies the quarantine). --artifact is handler-required so --json failures emit franken-node/trust-error-cli/v1 instead of a human clap error. --json emits franken-node/trust-quarantine-cli/v1. |
franken-node trust graph |
Ecosystem reputation graph from the verified trust-card registry and the project's package-lock.json: publisher→extension and lockfile depends_on edges, effective trust after named propagation rules (R1 revoked → 0; R2 quarantined ≤ 100; R3 at most 200 above the weakest dependency; R4 publishers with a revoked/quarantined extension capped at 400 and their extensions at publisher + 300), an explanation per lowered score, untracked dependencies reported as coverage gaps, and card-version transitions with the audit records that caused them. --extension <id> adds that extension's blast radius (every extension whose trust rests on it). --json emits franken-node/trust-graph-cli/v1. |
franken-node trust release |
Release a local trust-card hold with --artifact <extension-id or sha256:prefix>, preserving fleet quarantine owners and permanent revocation, or release a sentinel run-target record with --app. Exactly one target, --operator-id, and --reason are required. Artifact releases bind operator/rationale into signed card history and report remaining containment. --json emits franken-node/trust-artifact-release-cli/v1 for artifacts or franken-node/trust-release-cli/v1 for apps; failures emit franken-node/trust-release-error/v1. |
franken-node trust-card show <id> |
Show a card from the local trust-card registry JSON (not a live HTTP API). The extension id is handler-required so --json failures emit franken-node/trust-card-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node trust-card export <id> --json |
Export trust card as canonical JSON. The extension id is handler-required so --json failures emit franken-node/trust-card-error-cli/v1 instead of a human clap error. |
franken-node trust-card list |
List with filters: --publisher, --query, --page, --per-page. Flags: --json. |
franken-node trust-card compare <left> <right> |
Compare two trust postures. Left and right ids are handler-required so --json failures emit franken-node/trust-card-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node trust-card diff <id> <left_version> <right_version> |
Diff one card across versions. Id and versions are handler-required so --json failures emit franken-node/trust-card-error-cli/v1 instead of a human clap error. Flags: --json. |
Remote capabilities
| Command | Purpose |
|---|---|
franken-node remotecap issue |
Issue a local Ed25519 capability token. Does not perform the scoped network operation. --scope and --endpoint are handler-required so --json failures emit franken-node/remotecap-error-cli/v1 instead of a human clap error. Optional: --ttl, --issuer, --operator-approved, --single-use. Flags: --json (franken-node/remotecap-issue-cli/v1). |
franken-node remotecap verify |
Verify a capability token without using it. --token-file, --operation, and --endpoint are handler-required so --json failures emit franken-node/remotecap-error-cli/v1 instead of a human clap error. Flags: --json (franken-node/remotecap-verify-cli/v1). |
franken-node remotecap use |
Authorize a scoped network operation against a token (dry-run). Does not perform the HTTP request. --token-file, --operation, and --endpoint are handler-required so --json failures emit franken-node/remotecap-error-cli/v1 instead of a human clap error. Flags: --json (franken-node/remotecap-use-cli/v1). |
franken-node remotecap revoke |
Revoke a capability token. Required: --token-file. Flags: --json (franken-node/remotecap-revoke-cli/v1). |
Fleet quarantine control plane (file transport or live HTTP coordinator)
Every fleet command, and trust quarantine, talks to one of two stores:
- File transport (default). The node's own WAL-durable fleet store;
JSON reports
transport=file,live_control_plane=false,activated_source=file_transport_not_live. - Live coordinator. When
[fleet] control_plane_url(orFRANKEN_NODE_FLEET_CONTROL_PLANE_URL) names a node runningfleet serve, commands use its HTTP API with the bearer token from[fleet] control_plane_token_path(orFRANKEN_NODE_FLEET_CONTROL_PLANE_TOKEN_PATH). Reports carrytransport=http,live_control_plane=true,activated_source=http_control_plane_liveandcontrol_plane_url. A missing token, a refused token or an unreachable coordinator is an error; there is no silent fallback to the local store. Plaintexthttp://is accepted only for loopback; reach a remote coordinator overhttps://.
| Command | Purpose |
|---|---|
franken-node fleet serve |
Run the live coordinator: serves this node's durable fleet store over HTTP (fastapi_rust listener on the asupersync runtime). Routes: GET /v1/fleet/health (open), GET/POST /v1/fleet/actions, GET/POST /v1/fleet/nodes, GET /v1/fleet/state (bearer token, constant-time compare). Validates every record, caps bodies at 64 KiB, stamps heartbeats with its own clock. Flags: --bind (default 127.0.0.1:9440; port 0 picks a free port, printed first on stdout), --token-file (default .franken-node/keys/fleet-control-plane.token), --generate-token (create a 0600 256-bit token), --allow-non-loopback (plaintext listener; only behind TLS termination or an encrypted overlay), --max-requests, --json (JSONL FLEET_HTTP_LISTENING / FLEET_HTTP_REQUEST / FLEET_HTTP_SHUTDOWN events). |
franken-node fleet status |
Show fleet/quarantine state from the configured store. activated is true only when a live coordinator answered. Flags: --zone, --verbose, --json. |
franken-node fleet describe <node> |
Describe one fleet node with zone context and incident state. Flags: --zone, --json. |
franken-node fleet release |
Retire one fleet quarantine incident with signed receipts and application convergence. Preserves local quarantine, other scoped incidents, and permanent revocation. --zone disambiguates a shared incident ID; --zone all selects its global scope. --incident is handler-required so --json failures emit franken-node/fleet-error-cli/v1. |
franken-node fleet reconcile |
Reconcile the fleet-action log and wait for convergence. Times out fail-closed (same as fleet release). Flags: --json. |
franken-node fleet agent |
Pull fleet actions, apply them to this node's trust-card registry, and push a heartbeat each cycle. --zone is handler-required so --json failures emit franken-node/fleet-error-cli/v1 instead of a human clap error. Flags: --node-id, --poll-interval-secs, --max-cycles, --once, --json. |
# coordinator
franken-node fleet serve --bind 127.0.0.1:9440 --generate-token
# every other node (token copied from the coordinator)
export FRANKEN_NODE_FLEET_CONTROL_PLANE_URL=http://127.0.0.1:9440
export FRANKEN_NODE_FLEET_CONTROL_PLANE_TOKEN_PATH=/etc/franken-node/fleet.token
franken-node fleet agent --node-id edge-1 --zone us-east --poll-interval-secs 10
franken-node trust quarantine --artifact npm:@acme/auth-guard --json
franken-node fleet status --json # transport=http, live_control_plane=true
Incident replay and forensics
| Command | Purpose |
|---|---|
franken-node incident bundle |
Export a deterministic incident bundle. --id is handler-required so --json failures emit franken-node/incident-error-cli/v1. Reads --evidence-path or <project-root>/.franken-node/state/incidents/<slug>/evidence.v1.json. run automatically signs captured source evidence when SSRF checks, a denied host effect, or Sentinel trips; the incident id is INC-RUN-<host-effect chain head>. Captured sources must authenticate against the configured receipt authority before export. An export signing-key override does not replace that authority. Explicit operator-authored inputs remain supported; JSON reports source_authenticated. --verify checks the generated bundle. Flags: --json. |
franken-node incident replay |
Verifies recorded bundle integrity by default; --execute also re-executes captured native guest inputs. --bundle is handler-required so --json failures emit franken-node/incident-error-cli/v1 instead of a human clap error. Also fails closed without --trusted-public-key or --key-dir. Flags: --execute, --json. |
franken-node incident counterfactual |
Simulate alternative policy actions. Same trust-anchor requirement as replay. --bundle and --policy are handler-required so --json failures emit franken-node/incident-error-cli/v1 instead of a human clap error. Optional: --promote, --promotion-signing-key, --operator-id. --model synthetic (default) is the sandboxed risk-score stand-in. --model production re-decides every host effect a run-captured incident recorded (its signed effect-receipt chain entry) under the runtime profile named by --policy (strict, balanced, legacy-risky), using the run path's own capability table: an effect whose capability the profile does not grant is refused at the capability gate; one it grants gets the recorded outcome of the profile-independent SSRF / flow / sandbox gates; spawns keep their admission-bound outcome. Decisions after the first divergence are labelled determination=path_assumed. Bundles without recorded effects, and non-profile policies, are refused. The model is bound into the counterfactual digest. Flags: --json. |
franken-node incident list |
List recorded incidents. Filter: --severity. Flags: --json (failures franken-node/incident-error-cli/v1). |
franken-node incident capture |
Capture a recorded native run as incident evidence. --from-run <receipt-id|receipt-path> verifies the product-signed run record and complete receipt snapshot, then the engine ledger's counts, hash chain, detached signature, and product-root-signed session identity before sealing the captured source. An existing source is returned as already_captured only after its signature and run bindings authenticate. Legacy unsigned records require a new run. Flags: --severity, --json (franken-node/incident-capture-cli/v1). |
Long-term verifiability (LTV)
| Command | Purpose |
|---|---|
franken-node ltv attest |
Build self-contained long-term verification evidence from a signature-verified incident bundle: the bundle (plus, with --run-report, its run's host-effect chain hashes) becomes Merkle leaves, the root is re-attested and threshold-cosigned by operator witness keys. Required: --bundle, --out, at least one --witness-key, and a trust anchor (--trusted-public-key or --key-dir). --bundle, --out, and --witness-key are handler-required so --json failures emit franken-node/ltv-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node ltv verify-as-of |
Re-verify LTV evidence offline through the verifier SDK (verify_as_of_ltv): inclusion, re-attestation chain, witness threshold, and anteriority against --as-of. Fails closed (non-zero exit) on any failed assertion. Required: --evidence and --witness-anchor. Both are handler-required so --json failures emit franken-node/ltv-error-cli/v1 instead of a human clap error. Flags: --json. |
Runtime, safe-mode, and proofs
| Command | Purpose |
|---|---|
franken-node runtime lane status |
Emit the local default lane policy and an empty telemetry snapshot (not a running node). Flags: --json (failures franken-node/runtime-error-cli/v1). |
franken-node runtime lane assign <task_class> |
Assign one task class through a fresh default lane scheduler; the assignment is not persisted to a live node. Flags: --json (failures franken-node/runtime-error-cli/v1). |
franken-node runtime epoch |
Compare two caller-supplied epoch integers. Does not inspect a live ControlEpoch. --local-epoch is handler-required so --json failures emit franken-node/runtime-error-cli/v1 instead of a human clap error. Flags: --peer-epoch, --json. |
franken-node safe-mode enter |
Enter safe mode and block new runs within this directory and its descendants using persisted unsigned JSON operator state (not Ed25519-signed). --reason, --operator-id, and --trust-state-hash are handler-required so --json failures emit franken-node/safe-mode-cli/v1 instead of a human clap error. Reasons: explicit-flag, environment-variable, config-field, trust-corruption, crash-loop, epoch-mismatch. Flags: --json. |
franken-node safe-mode status |
Inspect persisted unsigned JSON safe-mode state. Flags: --json (franken-node/safe-mode-cli/v1). |
franken-node safe-mode exit |
Exit safe mode after explicit operator confirmation. --operator-id is handler-required so --json failures emit franken-node/safe-mode-cli/v1 instead of a human clap error. Required: --confirm. --trust-state-consistent, --no-unresolved-incidents, and --evidence-ledger-intact are operator attestations, not independently verified checks. Flags: --json. |
franken-node proofs queue status |
Inspect proof queue, proof status, and worker readiness from --input/--receipt snapshots (live_queue=false, queue_source=validation_readiness_snapshot). Not a live broker. Flags: --json. |
franken-node proofs workers restart |
Validate and emit a restart-request artifact. Does not restart a process unless --execute is set and FRANKEN_NODE_PROOF_WORKER_RESTART_EXECUTOR is bound. --operator-id, --operator-role, and --reason are handler-required so --json failures emit franken-node/proofs-error-cli/v1 instead of a human clap error. Required: --confirm. Flags: --json. |
Ops and diagnostics
| Command | Purpose |
|---|---|
franken-node ops health-check |
Process health from compile-time git SHA, evidence-ledger flush mtime, and persisted session files under .franken-node/state/sessions (not a live SessionManager). Flags: --json (franken-node/ops-health-check-cli/v1; session_count_source=persisted_session_files). |
franken-node ops resource-governor |
Advise whether validation should run / defer / deduplicate. Default observation is a live procfs sample; --process-snapshot is a fixture. Human output names source=. Flags: --json. |
franken-node ops validation-readiness |
Report validation evidence freshness from --input/--receipt snapshots, or an empty default snapshot when omitted. Human and --json name input_source and live_broker=false. Not a live broker. |
franken-node ops validation-closeout |
Render closeout summary from a --receipt snapshot (not a live broker). --bead-id and --receipt are handler-required so --json failures emit franken-node/ops-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node ops config-audit |
Audit active config across profiles. Flags: --json (franken-node/ops-config-audit-cli/v1). |
franken-node ops metrics |
Emit operator metrics (Prometheus by default). Flags: --json (franken-node/ops-metrics-cli/v1). |
franken-node ops incident-coverage |
Replay availability over retained durable run decisions and discoverable run records. Authenticated security outcomes determine the population; each counted capture must authenticate and each replayable bundle must match its source and trusted signature. JSON includes population_scope, authentication status, and source_errors. --min-coverage <0..1> fails when the population is empty, incomplete, unauthenticated, or below the floor. This local inventory does not establish deletion-resistant historical completeness. Flags: --json (franken-node/ops-incident-coverage-cli/v1). |
franken-node ops compat-corpus-run |
Run the committed compatibility corpus through bun + native franken-engine lockstep and write the digest-bound results artifact. --corpus-root and --out are handler-required so --json failures emit franken-node/ops-error-cli/v1 instead of a human clap error. --policy selects the profile the franken leg runs under (default legacy-risky, the artifact of record). The artifact's run_provenance block records the binary's sha256, the embedded git revision, the policy and the host's CPU count and load. Flags: --json (franken-node/ops-compat-corpus-run-cli/v1), --require-node-reference, --policy, --case-timeout-secs. |
franken-node ops proof-carrying-evidence |
Produce a 3-effect host-IO evidence chain from a real native-engine run. This is an additional L1 conjunct, not a substitute for corpus ≥95%. L1 GREEN still requires the compatibility corpus floor. Flags: --json, --out, --merge-corpus, --merge-l1-verdict. |
franken-node doctor workspace-pressure |
Probe local disk, memory, RCH slots, and build counts; apply balanced/conservative/permissive pressure policy. Default human report is stdout. --human-output <path> writes that report to a file (not a boolean flag). Flags: --json, --output, --human-output, --conservative, --permissive (failures franken-node/doctor-error-cli/v1). |
franken-node doctor close-condition |
Emit dual-oracle close-condition receipt. Human output names declared L1 pass_rate, node_canonical_observation_passes, node_canonical_unscored_fail_ids, and child_process_native_eval_aborts (those remain fail; not recategorized as pass). Flags: --json (failures franken-node/doctor-error-cli/v1). |
franken-node doctor evidence-readiness |
Report evidence readiness from an --input snapshot (not a live broker). --input is handler-required so --json failures emit franken-node/doctor-error-cli/v1 instead of a human clap error. Flags: --json. |
franken-node doctor process-spawn-readiness |
Securely resolve (or accept --bubblewrap-path for) a root-owned, non-setuid, non-writable Bubblewrap binary and run a bounded functional user/PID/cgroup/IPC/UTS namespace probe. Returns non-zero with unavailable or unsupported; readiness by itself does not grant process_spawn or replace the signed opt-in. Flags: --bubblewrap-path, --json (failures franken-node/doctor-error-cli/v1). |
Registry, bench, debug
| Command | Purpose |
|---|---|
franken-node registry publish <package> |
Admit a signed artifact into the local on-disk registry (not npm or a live control-plane publish). The package path and --version are handler-required so --json failures emit franken-node/registry-publish-error/v1 instead of a human clap error. Required: --version and --signing-key (raw Ed25519 32-byte key; hex, base64, or supported JSON wrapper). Optional: --max-active-artifacts. --json emits franken-node/registry-publish-cli/v1. |
franken-node registry search <query> |
Query locally stored registry artifacts with trust filters (not a live npm/control-plane service). The query is handler-required so --json failures emit franken-node/registry-error-cli/v1 instead of a human clap error. Filter: --min-assurance. --json emits franken-node/registry-search-cli/v1. |
franken-node registry verify <id> |
Verify a locally stored registry artifact. The extension id is handler-required so --json failures emit franken-node/registry-error-cli/v1 instead of a human clap error. --json emits franken-node/registry-verify-cli/v1. |
franken-node registry gc |
Archive older registry artifacts. Optional: --keep. --json emits franken-node/registry-gc-cli/v1. |
franken-node bench run |
Run benchmark suite and emit a SHA256 provenance_hash report (not Ed25519-signed). Flags: --scenario, --fixture-mode, --output, --json. The report is always JSON on stdout; --json suppresses the human summary on stderr. |
franken-node debug explain |
Walk a signed decision-receipt through verification steps. --receipt is handler-required so --json failures emit franken-node/debug-error-cli/v1 instead of a human clap error. --json emits machine-readable per-step results (franken-node/debug-explain-cli/v1). |
franken-node debug evidence |
Inspect verifier evidence artifacts. --artifact is handler-required so --json failures emit franken-node/debug-error-cli/v1 instead of a human clap error. --kind accepts auto, node-replay-capsule, provenance-attestation, vef-evidence-capsule. --json emits machine-readable diagnostics. |
franken-node debug trace |
Trace a policy-activation fixture through doctor_policy_activation (not live guest execution). --policy and --input are handler-required so --json failures emit franken-node/debug-error-cli/v1 instead of a human clap error. --json emits machine-readable trace (franken-node/debug-trace-cli/v1). |
[!NOTE] Commands that declare
--jsonemit machine-readable output. That flag is not on every subcommand.bench runalways writes the JSON report (provenance_hashSHA256 content hash, not Ed25519-signed) to stdout;--jsonis accepted for operator/agent wrappers and suppresses the human summary on stderr. Many JSON-capable commands also accept--structured-logs-jsonlfor event-coded diagnostics on stderr (see Structured Logging and Observability), and--trace-id <id>to correlate log events across processes.
[!WARNING] Fail-closed commands.
incident replayandincident counterfactualrefuse to run without--trusted-public-keyor--key-dir.registry publishrefuses without--versionand--signing-key.verify releaserefuses without--key-dir.safe-mode enterrequires--reason,--operator-id, and--trust-state-hash. There are no built-in trust roots and no implicit defaults; this is intentional, not an ergonomics gap.
Configuration
franken_node.toml (discovered from the working directory, the
project root, or the path passed to --config):
# Runtime profile: strict | balanced | legacy-risky
profile = "balanced"
[compatibility]
# API compatibility mode for migration and runtime dispatch
mode = "balanced"
# Divergence receipts are always recorded in production profiles
emit_divergence_receipts = true
# TTL for signed compatibility receipts
default_receipt_ttl_secs = 3600
[migration]
# Enable automatic rewrite suggestions
autofix = true
# Doctor WARN when false. `migrate validate` does not invoke `verify lockstep`.
require_lockstep_validation = true
[trust]
# Revocation freshness requirements by action class
risky_requires_fresh_revocation = true
dangerous_requires_fresh_revocation = true
# Quarantine defaults
quarantine_on_high_risk = true
[replay]
# Persist high-severity replay artifacts
persist_high_severity = true
# Deterministic bundle export format version
bundle_version = "v1"
# Maximum permitted replay capsule freshness window
max_replay_capsule_freshness_secs = 3600
[registry]
# Enforce signature and provenance gates
require_signatures = true
require_provenance = true
minimum_assurance_level = 3
[fleet]
# Optional override for the persisted fleet transport state root
state_dir = ".franken-node/state/fleet"
# Fleet convergence timeout for quarantine/release operations
convergence_timeout_seconds = 120
[observability]
# Stable metrics namespace for automation
namespace = "franken_node"
emit_structured_audit_events = true
[remote]
# Default TTL for remote idempotency entries
idempotency_ttl_secs = 604800
[security]
# Maximum degraded-mode duration before suspension
max_degraded_duration_secs = 3600
# Child-process spawning has no profile default. An operator-issued opt-in is
# represented by [security.child_process_spawn] plus its nested .token table.
# The token subject must equal the policy subject reported by trusted issuance
# tooling after all non-recursive fields below are populated, and the signature
# must cover the CapabilityToken content hash. The operator trust anchor is a
# separate keyring file; it is never accepted from this project config.
# backend = "bubblewrap"
# binary_path = "/usr/bin/bwrap"
#
# Process execution authority is also explicit and token-bound: each command
# alias resolves without PATH lookup to one exact canonical executable and
# SHA-256 digest. The cwd jail, environment vocabulary, shell choice, and
# resource ceilings below are all part of the signed policy subject.
#
# [security.child_process_spawn.execution_policy]
# jailed_cwd_root = "/srv/franken-node/apps/example"
# allow_shell = false
# allowed_env_keys = ["LANG"]
# fixed_env = { LANG = "C.UTF-8" }
#
# [security.child_process_spawn.execution_policy.allowed_executables.true]
# path = "/usr/bin/true"
# sha256 = "<64 lowercase hexadecimal characters>"
#
# [security.child_process_spawn.execution_policy.limits]
# max_children = 4
# max_executable_path_bytes = 4096
# max_executable_bytes = 134217728
# max_argv_count = 128
# max_argv_bytes = 65536
# max_env_count = 128
# max_env_bytes = 65536
# max_cwd_bytes = 4096
# max_prelaunch_bytes = 131072
# max_request_bytes = 16777216
# max_stdin_bytes = 1048576
# max_output_bytes = 4194304
# max_runtime_millis = 30000
[security.network_policy]
# SSRF enforcement mode: none | monitor | block
ssrf_enforcement = "block"
# Keep the legacy boolean guard enabled while migrating older configs.
ssrf_protection_enabled = true
block_cloud_metadata = true
audit_blocked_requests = true
[engine]
# Optional path override for the franken_engine binary
# binary_path = "/usr/local/bin/franken-engine"
[runtime]
# Preferred runtime: auto | node | bun | franken-engine
preferred = "auto"
# Global cap on in-flight network-bound operations
remote_max_in_flight = 50
# Retry hint when bulkhead is saturated
bulkhead_retry_after_ms = 50
# Optional static process metadata reads in native entrypoint code, including
# supplied process.argv values and process.platform. Omitted: denied in
# strict/balanced, allowed in legacy-risky. Explicit false denies every profile.
# allow_process_shape = true
# Also FRANKEN_NODE_RUNTIME_ALLOW_PROCESS_SHAPE; environment overrides TOML.
# Replay capture retains supplied arguments and this bounded grant. It does
# not grant environment values, filesystem, network, or child-process access.
# Instruction budget for one `run` in the native engine (optional; default
# strict 200M, balanced 1B, legacy-risky 5B). Also
# FRANKEN_NODE_RUNTIME_MAX_INSTRUCTIONS. The wall-clock timeout still applies.
# max_instructions = 1_000_000_000
# Optional per-frame register and guest call-depth ceilings on both lanes.
# Accepted ranges: registers 1..=65_536; call depth 1..=10_000.
# max_registers = 2_048
# max_call_depth = 128
# Also FRANKEN_NODE_RUNTIME_MAX_REGISTERS and
# FRANKEN_NODE_RUNTIME_MAX_CALL_DEPTH; environment overrides TOML.
# Optional positive execution ceilings on both native lanes; omitted values
# preserve the engine defaults listed under Runtime Profiles.
# max_heap_objects = 200_000
# max_total_memory_bytes = 100_663_296
# max_console_entries = 2_000
# Accounted memory excludes console and is not process RSS. Console bytes
# retain the separate fixed engine ceiling; overflow fails without eviction.
# Independent per-module parser limits, including required/imported modules.
# Defaults are listed under Runtime Profiles. Accepted override ranges:
# source bytes 1..=2_097_152; tokens 1..=131_072. Invalid values fail closed.
# A balanced-profile opt-in for larger npm modules, without changing profile:
# max_parse_tokens = 131_072
# max_parse_source_bytes = 1_048_576
# Also FRANKEN_NODE_RUNTIME_MAX_PARSE_TOKENS and
# FRANKEN_NODE_RUNTIME_MAX_PARSE_SOURCE_BYTES; environment overrides TOML.
[runtime.lanes.cancel]
max_concurrent = 12
priority_weight = 100
queue_limit = 24
enqueue_timeout_ms = 25
overflow_policy = "reject"
[runtime.lanes.realtime]
max_concurrent = 24
priority_weight = 60
queue_limit = 48
enqueue_timeout_ms = 75
overflow_policy = "enqueue-with-timeout"
[thresholds]
# Algorithmic/statistical thresholds (all optional, with safe defaults)
max_failure_rate = 0.05
min_quality_score = 0.8
max_variance_pct = 5.0
regression_threshold_pct = 10.0
franken-node init writes starter configuration files with the active
profile and, unless --no-state is supplied, bootstraps the default state
layout under --state-dir, --out-dir, or the current directory, in that
order. The config is written to --out-dir, or else beside that state: state
bootstrap signs the trust-card registry with the config's registry key, so the
two must travel together. Only init --no-state without --out-dir prints
the resolved starter TOML to stdout instead of writing it. franken-node ops config-audit --json prints the resolved
configuration with profile overlays applied.
Crate Features
The frankenengine-node crate ships a granular feature surface. Defaults
include native execution, HTTP/process helpers and the fleet HTTP server;
additional product surfaces opt in explicitly.
| Feature | Default | Purpose |
|---|---|---|
engine |
✅ | Links the sibling frankenengine-engine and frankenengine-extension-host crates. |
http-client |
✅ | Outbound HTTP client support (ureq, url). |
external-commands |
✅ | External command/process helpers (which, ctrlc). |
fleet-control-plane-server |
✅ | Live fleet serve HTTP coordinator on fastapi_rust/asupersync over the durable fleet store; implies http-client. |
extended-surfaces |
opt-in | Legacy umbrella over the granular product features below. |
control-plane |
opt-in | API middleware, fleet operations, control-plane functionality. |
policy-engine |
opt-in | Security policies, guardrail monitors, hardening state machines. |
remote-ops |
opt-in | Remote operations, distributed coordination, eviction sagas. |
admin-tools |
opt-in | Enterprise governance, migration tools, administrative functionality. |
verifier-tools |
opt-in | Verifier-specific tooling, economy, and SDK surfaces. |
advanced-features |
opt-in | Claims, conformance, encoding, extensions, federation, performance, and repair surfaces. |
test-support |
opt-in | Shared test helpers (enables control-plane and admin-tools). |
loom-models |
opt-in | Library-level #[cfg(loom)] helpers for explicit Loom invocations. |
asupersync-transport |
opt-in | Direct asupersync transport integration. |
compression |
opt-in | Gzip/deflate support via flate2. |
cbor-serialization |
opt-in | CBOR encoding support via ciborium. |
blake3 |
opt-in | Optional BLAKE3 hashing support. |
profiling |
opt-in | Per-hot-path latency histograms via hdrhistogram; absent from default builds. |
# Enable everything except packaging extras
cargo build --release -p frankenengine-node --features extended-surfaces
# Build a minimal node binary with no product surfaces
cargo build --release -p frankenengine-node --no-default-features --features engine
Architecture
flowchart TB
fn["<b>franken_node</b><br/>compatibility, migration, trust UX, ops<br/>verifier SDK, fleet control,<br/>recorded incident bundles"]
asu["<b>asupersync</b><br/>runtime for the default fleet server<br/>additional transport integration is opt-in"]
ftui["<b>frankentui</b><br/>Buffer echo of<br/>preformatted doctor/fleet lines"]
fapi["<b>fastapi_rust</b><br/>default fleet HTTP listener<br/>authenticated durable-store routes"]
feng["<b>franken_engine</b><br/>native JS / TS runtime<br/>+ extension host"]
fsql["<b>frankensqlite</b><br/>published fsqlite dependency<br/>durable trust, fleet and evidence stores"]
fn --> ftui
fn --> fapi
fn --> feng
fn --> fsql
fapi --> asu
classDef product fill:#1f6feb,color:#fff,stroke:#0a3b91,stroke-width:1px;
classDef plane fill:#5b3cc4,color:#fff,stroke:#3a2780,stroke-width:1px;
classDef substrate fill:#27c93f,color:#0b1020,stroke:#1d8e2c,stroke-width:1px;
classDef store fill:#f0c674,color:#0b1020,stroke:#a8884c,stroke-width:1px;
class fn product;
class asu,ftui,fapi plane;
class feng substrate;
class fsql store;
Repository layout
| Path | Purpose |
|---|---|
Cargo.toml |
Workspace manifest (crates/franken-node, crates/franken-security-macros, sdk/verifier) |
crates/franken-node/src/main.rs |
Binary entrypoint and CLI dispatch |
crates/franken-node/src/lib.rs |
Library exports, bounded I/O helpers, lock utilities |
crates/franken-node/src/cli.rs |
Stable CLI argument shapes (the contract downstream tests assert) |
crates/franken-node/src/config.rs |
Config discovery, profile resolution, CLI override merging |
crates/franken-security-macros/ |
Internal proc-macro helpers |
sdk/verifier/src/lib.rs |
Public verifier-facing SDK (frankenengine-verifier-sdk) |
docs/specs/** |
Product contracts (gates, schemas, runbooks) |
tests/**, crates/franken-node/tests/** |
Integration, conformance, contract, e2e, golden, security, perf tests |
scripts/check_*.py |
430+ Python validators wired into CI gates |
.github/workflows/ |
Gate-oriented CI: claim gates, conformance gates, closer-discipline, mutants, coverage, security-golden-artifacts, etc. |
packaging/profiles.toml |
Packaging profile metadata (local, dev, enterprise) |
Core source domains
| Domain | What lives there |
|---|---|
cli, config, main |
CLI surface, profile resolution, structured logging |
runtime/ |
Epoch transitions, lane scheduler/router, safe-mode controller, bulkhead, resource governor, lockstep harness, hardware planner, speculation |
control_plane/ |
Control epoch barriers, fork detection, audience tokens, MMR proofs, marker stream, divergence gate, evidence replay gate, key-role separation |
security/ |
Constant-time comparisons, revocation freshness, threshold signatures, remote capabilities, sandbox policy compiler, SSRF policy, sybil defense, zk attestation, challenge flow, DGIS, BPET |
supply_chain/ |
Trust cards, certification, camouflage assessment, attestation manifest |
migration/ |
Migration audit, rewrite, validate, BPET migration gate, deterministic audit logs |
replay/ |
Time-travel engine, replay verdicts, divergence detection |
tools/ |
Replay bundle builder with fsync-backed durability |
vef/ |
Verifiable Execution Fingerprint: proof service, generator, scheduler, verifier, receipt chain |
dgis/ |
Dependency Graph Immune System: contagion simulator, fragility model, SPOF detection |
atc/ |
Autonomous Trust Control surface contract and fingerprinting |
federation/ |
Privacy-preserving cross-deployment threat learning, ATC↔BPET↔DGIS bridges |
observability/ |
Evidence ledger, durability violation detection, witness ref tracking |
ops/ |
Validation broker / planner / proof cache / debt ledger, doctor, evidence index, cleanup executor |
extensions/, registry/ |
Signed extension publication, schema enforcement, GC, search |
connector/ |
Adapter layer (CRDT, lease coordinator, durability, ecosystem compliance, rollout state, verifier SDK glue) |
remote/, api/ |
Remote capability lifecycle, fleet quarantine API surface, session auth |
storage/ |
frankensqlite adapter, retrievability gate, cleanup receipts |
verifier_economy/, claims/, encoding/, repair/ |
Reputation/staking, claim compiler, canonical serialization, recovery orchestration |
sdk/verifier/ |
External verifier SDK (bundle, capsule, counterfactual, top-level lib.rs) |
Trust-State Lifecycle
A trust card moves through a small set of operationally meaningful
phases. These labels are descriptive, not enum variants in the source.
What the source models concretely is the card's
user_facing_risk_assessment, revocation_status, audit_history,
camouflage_hints, and reputation_trend; the diagram below shows
how those fields evolve over the lifecycle of a single card. The
registry separately validates the card snapshot under a
SnapshotSourceContext (TrustedFile for local loads, UntrustedNetwork
for remote refreshes), which picks the validation strategy without
being stored on the card itself.
stateDiagram-v2
[*] --> Seeded: franken-node trust scan
Seeded --> Active: trust sync (refresh from OSV)
Active --> Consumed: revocation gate passes,<br/>action executes
Active --> Refused: revocation frontier stale,<br/>signed refusal receipt
Consumed --> RiskBumped: CamouflageHint severity<br/>crosses threshold
Active --> RiskBumped: same
RiskBumped --> RevokedQuarantined: trust quarantine /<br/>trust revoke
Active --> RevokedQuarantined: same
Consumed --> RevokedQuarantined: same
RevokedQuarantined --> Active: fleet release<br/>(after remediation)
Refused --> Active: trust sync --force
Every transition is recorded in the card's audit_history and (for
fleet-visible transitions) in the evidence ledger. A card's full history
is replayable: an auditor can reconstruct exactly which decisions ran
under which snapshot.
Trust-Native Primitives
Every primitive below is implemented in crates/franken-node/src/ and
has integration or conformance tests under tests/ and
crates/franken-node/tests/. Being implemented is not the same as being
used: the last column says whether a command of the default binary (default
features) actually calls it, from a static trace of the command handlers on
2026-09-25 (bd-reality-20260923-26n9r.14). "Library only" means compiled but
reached by no command; feature: means compiled only with that opt-in
feature.
| Primitive | Module | Properties | Reached by the default CLI |
|---|---|---|---|
| Trust cards | supply_chain::trust_card |
Publisher identity, risk assessment, audit history, version tracking, camouflage hint, HMAC-signed snapshots, trusted-vs-untrusted source context | Yes: trust, trust-card, run preflight. Camouflage marking is library only |
| Revocation freshness gates | security::revocation_freshness{_gate} |
SafetyTier-based (Standard/Risky/Dangerous) age policies; now >= expires_at fail-closed semantics; bound to capability issuance |
revocation_freshness: yes (run preflight, remotecap issue). revocation_freshness_gate: library only |
| Fleet quarantine state machine | api::fleet_quarantine, control_plane::fleet_transport, control_plane::fleet_http_server, control_plane::fleet_transport_http |
QuarantineScope, FleetAction (quarantine/revoke/release), signed DecisionReceipt, ConvergencePhase tracking, durable transport, live HTTP coordinator |
Partly: fleet serve/agent/status/release/reconcile and trust quarantine run over the durable store or a live coordinator with signed decision receipts; FleetControlManager and the release-quorum logic are library only |
| Ecosystem reputation graph | supply_chain::trust_graph |
Publisher / extension / lockfile-dependency graph; rule-based trust propagation with per-node explanations; card-version transitions with audit causes; blast radius | Yes: trust graph |
| Deterministic incident replay | replay::time_travel_engine, tools::replay_bundle |
WorkflowTrace capture, environment snapshot, schema versioning, ReplayVerdict, divergence detection, fsync-backed durable serialization |
tools::replay_bundle: yes (incident bundle/replay, run incident capture). time_travel_engine: library only |
| Counterfactual simulator | tools::counterfactual_replay, replay::time_travel_engine + sdk/verifier |
Re-decide a recorded incident under an alternative --policy; emit diff of decisions, blocked actions, and evidence |
incident counterfactual: --model production re-decides run-captured host effects with the run path's per-profile capability table; --model synthetic scores recorded evidence. time_travel_engine is library only |
| Compatibility lockstep oracle | runtime::lockstep_harness, api::compat_gate |
verify lockstep defaults to Bun+franken dyad; corpus L1 treats Node as spec (matching only Bun stays fail; child_process aborts remain fail) |
lockstep_harness: yes (verify lockstep). compat_gate: feature:control-plane |
| Migration autopilot | migration::*, BPET migration gate |
Audit → rewrite → validate → rollout; unsigned JSON MigrationRollbackPlan (not Ed25519-signed); migrate validate is static+smoke, not verify lockstep |
migration::*: yes (migrate). BPET migration gate: feature:admin-tools |
| Signed extension registry | supply_chain::extension_registry; registry::*, extensions::artifact_contract |
Ed25519-signed artifacts, schema enforcement, assurance levels, GC, search | Yes: the registry commands use supply_chain::extension_registry. registry::*: feature:admin-tools; artifact_contract: feature:advanced-features |
| Threshold signature verification | security::threshold_sig |
k-of-n quorum, cached configurations, domain-separated and constant-time verification | Library only |
| MMR proofs | control_plane::mmr_proofs |
Merkle-Mountain-Range inclusion/prefix proofs, raw-hash internals, rebuild/sync from marker streams | Library only |
| Audience tokens | control_plane::audience_token |
Expiry, attenuation, domain separation, token chains with depth/root/leaf accessors, replay-resistant nonce window | Library only |
| Fork detection | control_plane::fork_detection |
State-vector hashing, rollback proofs, marker-proof verifier, DetectionResult (Converged/Forked/GapDetected/RollbackDetected) |
Library only |
| Control epoch barriers | control_plane::control_epoch, epoch_transition_barrier |
Validity-window policy, EpochRejectionReason enum, fail-closed artifact rejection |
Library only (runtime epoch compares integers you pass it) |
| Evidence ledger | observability::evidence_ledger |
Append-only Ed25519-signed decision log, hash-chain prev-entry linkage, replay-attack detection, bounded capacity with eviction, optional spill-to-disk | Yes: run (including preflight refusals), trust revoke, trust quarantine, trust release and fleet release append signed, hash-chained entries to .franken-node/state/evidence-ledger.db (fleet decisions go beside the fleet state directory); verify transparency-log checks it |
| Remote capability tokens | security::remote_cap, remote::* |
Scope-bound, single-use-optional Ed25519 tokens with endpoint binding | Yes: remotecap, trust scan --deep/--audit, trust sync, init |
| DGIS adversarial topology | security::dgis, dgis::* |
Dependency contagion simulator, fragility model, SPOF detection, immunization planner | Partly: the fragility model scores npm maintainer data in trust scan --deep; the contagion simulator, SPOF detection and immunization planner are library only |
| BPET evolution risk scorer | security::bpet, migration::bpet_migration_gate |
Phenotype feature extraction, topology risk delta during rollout | Library only (bpet_migration_gate: feature:admin-tools) |
| ATC adversarial trajectory checker | security::trajectory_gaming + federation::atc_* |
Camouflage detection severity, ATC participation weighting, reciprocity tracking | Partly: trust observe authenticates collector-attributed package measurements and persists BPET camouflage findings on trust cards; federation weighting/reciprocity remains library only (feature:advanced-features) |
| VEF execution receipts | vef::* |
Proof service / generator / scheduler / verifier, linked receipt chain, fail-closed verification | Partly: debug evidence verifies vef::evidence_capsule; the proof service, scheduler, verifier and receipt chain are library only |
| Verifier SDK | sdk/verifier (frankenengine-verifier-sdk) |
Independent bundle replay, capsule verification, counterfactual reasoning outside the producing runtime | Yes: ltv attest/verify-as-of call it; incident bundles the CLI writes verify with its verify_incident_bundle; it also builds standalone |
The Trust Gradient
Trust in franken-node is graduated rather than binary. The diagram is
the full model for a signed extension; not every command passes every
gate. run checks the dependency's trust card, the revocation frontier
and the runtime profile. Registry admission, provenance and capability
scope apply to the registry and remotecap commands, not to npm
dependencies at run. The gates layer:
flowchart TD
A["Publisher identity<br/>Ed25519 signing key"] --> B["Signed artifact in registry<br/><code>--signing-key</code>, <code>--version</code>"]
B --> C["Provenance + assurance check<br/><code>require_provenance</code>, <code>minimum_assurance_level</code>"]
C --> D["Trust card snapshot<br/>publisher, version, audit history, camouflage"]
D --> E["Revocation freshness gate<br/><code>SafetyTier</code> vs. frontier age"]
E --> F["Capability scope<br/>audience token + endpoint binding"]
F --> G["Runtime policy profile<br/>strict | balanced | legacy-risky"]
G --> H["Decision receipt<br/>run outcomes appended to the evidence ledger"]
classDef identity fill:#1f6feb,color:#fff,stroke:#0a3b91;
classDef storage fill:#5b3cc4,color:#fff,stroke:#3a2780;
classDef policy fill:#f0c674,color:#0b1020,stroke:#a8884c;
classDef audit fill:#27c93f,color:#0b1020,stroke:#1d8e2c;
class A,B identity;
class C,D storage;
class E,F,G policy;
class H audit;
Each layer can fail closed independently. The gate-by-gate decomposition is what enables the counterfactual simulator to answer "would tightening one gate have prevented this?" rather than just "was this incident preventable?".
The same model in words:
- Identity. Every publish path requires an Ed25519 signing key and
an explicit
--version. There is no anonymous publish path. - Storage integrity. The registry recomputes the artifact hash on read and verifies the signature in constant time.
- Provenance.
registry.require_provenance = truerejects artifacts that did not arrive with attestation metadata. - Reputation. Trust cards aggregate what scans and syncs observe (vulnerabilities, typosquats, package age, maintainer fragility). Camouflage assessment can raise a card's risk, but no command feeds it trajectory data yet.
- Currency. Revocation freshness gates refuse risky and dangerous actions when the local frontier is older than policy allows.
- Audience. Capability tokens are bound to specific endpoints and operations; a token issued for one endpoint cannot be used at another.
- Profile. The operator-selected runtime profile sets thresholds for every gate above, all at once.
- Audit. Decisions leave receipts: every
runwrites a SHA-256 execution receipt,trust revoke/trust quarantine/incident bundleexport signed decision receipts with--receipt-out,fleet release/reconcilesign their decision and convergence receipts, and sentinel escalations are signed. Eachrunalso appends a signed, hash-chained entry (receipt id and hash, exit code, containment verdict, host-effect chain head) to the durable evidence ledger, as do preflight refusals,trust revoke/quarantine/releaseandfleet release;verify transparency-log .franken-node/state/evidence-ledger.db --public-key .franken-node/keys/receipt-signing.pubchecks the chain.
Remote Capability Operations
A capability token is bound to a specific remote operation plus an
endpoint. The operation vocabulary is enumerated in
security::remote_cap::RemoteOperation:
| Operation | Canonical name | Used for |
|---|---|---|
NetworkEgress |
network_egress |
Generic outbound network calls |
FederationSync |
federation_sync |
Pulling federated state from a peer deployment |
RevocationFetch |
revocation_fetch |
Pulling fresh revocation frontiers |
RemoteAttestationVerify |
remote_attestation_verify |
Verifying an attestation served by a remote service |
TelemetryExport |
telemetry_export |
Shipping telemetry to an external sink |
RemoteComputation |
remote_computation |
Off-host compute (e.g., remote proof generation) |
ArtifactUpload |
artifact_upload |
Publishing artifacts to a remote registry |
The capability gate also tracks a ConnectivityMode:
Connected: outbound network is permitted under policy.LocalOnly: every remote operation fails closed regardless of the capability token; useful in safe mode or for offline replays.
RemoteScope aggregates the set of allowed operations on a token.
Single-use tokens (remotecap issue --single-use) burn their scope
after one accepted use; reuse fails closed. Token use also re-validates
the resolved IP set so a token issued for one endpoint cannot be
silently redirected by DNS changes (see Network Egress and SSRF Policy).
Capability Artifact Format
Extensions that want to declare exactly what capabilities they expect
ship a capability artifact alongside their package. The format is
governed by extensions::artifact_contract with
SCHEMA_VERSION = "capability-artifact-v1.0". The shape:
| Type | Purpose |
|---|---|
CapabilityEntry |
One requested capability with bounded inputs (ARTIFACT_TOKEN_INPUT_POLICY) |
CapabilityContract |
The full list of CapabilityEntry items the extension declares; subject to ARTIFACT_CAPABILITY_LIST_POLICY length bounds |
ExtensionArtifact |
The wire-format artifact: contract + signature + provenance metadata |
AdmissionDenialReason |
Typed refusal reason emitted when the registry refuses admission |
The module exports stable event_codes, error_codes, and invariants
sub-modules so a SIEM filter or auditor can pin on the codes without
parsing message text. Admission is fail-closed: every refusal produces
a typed AdmissionDenialReason and (where appropriate) a signed
decision receipt.
franken-node registry publish accepts the artifact alongside the
package; franken-node registry verify <id> recomputes the contract
hash and validates the signature in constant time.
How the Primitives Work
Trust cards
A trust card is a per-extension record carrying publisher identity, audit
history, version chain, vulnerability state, camouflage assessment, and
revocation status. Cards are HMAC-signed snapshots; the registry loads
them under a SnapshotSourceContext of either TrustedFile
(operator-managed local loads, lazy validation) or UntrustedNetwork
(remote refreshes during trust sync, eager pre-parse signature check
and a hard JSON size cap). The card itself carries card_hash and
registry_signature so its integrity is verifiable regardless of which
context loaded it.
Camouflage assessment is produced by security::bpet::camouflage_detector
and surfaced through security::trajectory_gaming::CamouflageHint, whose
kind is one of PhaseShift, Dropout, DistributionMismatch, or
GradualCreep. Each hint carries a numeric severity and an evidence
map. supply_chain::trust_card::apply_camouflage_assessment raises the
card's risk class when severity crosses the configured threshold. Hints
accumulate across snapshots so future evaluations see the trajectory, not
just the latest sample.
Revocation freshness gates
security::revocation_freshness defines a SafetyTier enum (Standard,
Risky, Dangerous) and a per-tier maximum age policy. Before a risky or
dangerous action runs, the runtime asks: is the local revocation frontier
fresher than policy.max_age_for(tier)?
- The revocation frontier is a value recorded in the durable trust
registry: the time of the last
trust sync --forcein which every trust signal was fetched from the network without error. It is never inferred from file timestamps, which the database rewrites even on read-only access. A missing frontier counts as stale. runapplies the gate when it admits a dependency on the strength of its trust card, both at preflight and again at execution time (TOCTOU recheck).strictmaps to Dangerous (5 min) and refuses a stale or missing frontier (revocation_stale).balancedmaps to Risky (1 h) and admits with a preflight warning instead.legacy-riskyis Standard, with no age floor.- Declared dependencies with no trust registry at all block
rununderstrict/balanced(registry_missing);legacy-riskywarns. - A dependency whose trust card is High or Critical risk (known advisories
from
trust sync/trust scan --audit, or a suspected typosquat) blocksrununderstrict(high_risk).balancedadmits it with a preflight warning. remotecap issueis always Dangerous: it needs a frontier recorded within the last 5 minutes in the working directory's registry. The local revoked-token list (remotecap revoke) is authoritative and is not treated as a freshness signal.- If the gate fails the action does not execute; the operator gets an
explicit
revocation freshness gate denied …error and atrust sync --forcerecommendation.
Threshold signatures (k-of-n)
security::threshold_sig verifies signatures where any k of n named
signers must produce a valid Ed25519 signature over the canonical message
form. The verification path:
- Canonicalize the input with a domain separator (
b"threshold_sig_verify_v2:"). - Length-prefix each variable-length field individually.
- Decode each presented signature and run the Ed25519 verify in constant time against the corresponding cached public key.
- Tally successes; require at least
k. Tally is bounded bynand usessaturating_addto defeat overflow-based bypass.
The verification path is the verify_threshold / verify_threshold_cached
public API in security::threshold_sig; the cached form pre-decodes the
signer set once and re-uses it across many verifications of the same
quorum.
MMR proofs (Merkle Mountain Range)
control_plane::mmr_proofs maintains an append-only log shaped as an MMR.
Each leaf is the canonical hash of an evidence entry; each peak is a
binary tree over the leaves under it. Two proofs are supported:
- Inclusion: Given a leaf and the current peaks, prove the leaf is at
position
i. Verifier reconstructs the peak hashes from sibling hashes along the path and compares against the published root. - Prefix: Given a prior root at size
s1and the current root at sizes2 >= s1, prove the log only ever grew (the prefix ats1is unchanged). Verifier reconstructs old peaks from new ones and bagged intermediate hashes.
Hash inputs are domain-separated (b"mmr_proofs_leaf_v1:",
b"mmr_proofs_node_v1:", b"mmr_proofs_v1:") and length-prefixed.
Internal storage uses raw hashes to avoid per-step allocation in proof
generation.
Audience tokens
control_plane::audience_token::AudienceBoundToken binds a credential to
one or more audiences (the intended recipient service identifiers).
The canonical fields:
token_id,issuer: bounded-length identifiers.audience: Vec<String>: every recipient that may legitimately accept this token. A relying party whose identity is not in the list rejects it.capabilities: BTreeSet<ActionScope>: granted action scopes, strictly attenuated on each delegation.issued_at,expires_at: UTC millis; fail-closed whennow >= expires_at.nonce: replay-detection nonce. The runtime consults a bounded recently-seen window so replays are caught even if the issuer reuses an ID across epochs.parent_token_hash: Option<String>: the hash of the parent token (Nonefor root tokens). A sub-token inherits and may only attenuate.max_delegation_depth: u8: caps how many further delegations are allowed;0means the token cannot be further delegated.
The signature is verified in constant time against the issuer's public
key. The canonical preimage begins with the domain separator
b"audience_bound_token_signature_v1:", then length-prefixes every
variable-length field (token_id, issuer, each audience entry, each
capability label, nonce, parent hash) and little-endian-encodes the
numeric fields.
Fork detection
control_plane::fork_detection maintains a state vector (a hash
commitment over each replica's observable state at a given epoch) plus
a parent-hash chain that pins the rollback boundary. On each tick, the
runtime compares its state vector against peers received via the control
plane and emits one of four DetectionResult outcomes:
Converged: both replicas share identical state at the same epoch.Forked: same epoch but different state hashes. The runtime emits a divergence receipt for replay.GapDetected: epoch difference exceeds one. Reconciliation is required before further decisions can be issued.RollbackDetected: the parent-hash chain is broken, indicating an unauthorized rollback. The runtime refuses to accept the offending state and the operator must investigate.
Each detection result carries a stable label (CONVERGED, FORKED,
GAP_DETECTED, ROLLBACK_DETECTED) suitable for logs and metrics. Only
Converged is treated as safe; the other three trigger evidence emission
and gate subsequent decisions.
Control epoch barriers
control_plane::control_epoch defines a monotonic epoch counter that
advances on every control-plane transition (policy change, fleet action,
schema migration). Each artifact carries an epoch field. Before applying
an artifact:
- The validity window check confirms the artifact's epoch is acceptable relative to the current epoch.
- Counter arithmetic uses
saturating_addto defeat overflow-based bypass. - Rejected artifacts emit a typed
EpochRejectionReason, one ofInvalidArtifactId,FutureEpoch, orExpiredEpoch, and are recorded asEpochArtifactEvententries so post-mortems can trace why the artifact was refused. Each variant maps to a stable label (EPOCH_REJECT_INVALID_ARTIFACT_ID,EPOCH_REJECT_FUTURE,EPOCH_REJECT_EXPIRED) for downstream consumers.
DGIS contagion simulator
dgis::contagion_simulator::simulate takes a ContagionGraph (defined
in the sibling module dgis::contagion_graph with ContagionEdge and
EdgeKind types) plus an initial_infected: &[NodeId] slice and a
SimulatorConfig, and simulates adversarial spread against a configured
ContagionProfile. Each tick advances an InfectionState
tracking per-node exposure (exposure_of(node)), the set of currently
infected nodes, and a saturating step counter. The simulator is
deterministic: a fixed seed and profile yield an identical
SimulationTrace, so a snapshot can be replayed bit-exactly.
Termination is classified by TerminationReason and reported alongside
the trajectory. The simulator sits next to two complementary DGIS
modules in the same crate: dgis::fragility_model (whose
FragilityFactor, FragilityScore, MaintainerProfile, and
PublisherProfile types classify per-node brittleness against
maintainer / publisher signals) and dgis::spof_detection (which
consumes the fragility model to pinpoint single points of failure).
A separate security::dgis::update_copilot module produces
TopologyRiskMetrics that the BPET subsystem can ingest via
security::bpet::dgis_fusion; the BPET evolution risk scorer described
below operates on those topology metrics rather than on the contagion
simulator's InfectionState directly.
BPET evolution risk scorer
trust observe connects signed, collector-attributed package measurements to
the BPET camouflage detector and durable trust cards. Four comparable samples
enable trajectory analysis; observed camouflage can raise the package's risk.
The command checks an independently pinned collector key, exact version and
artifact bindings, measurement continuity, and observation replay protection.
See Signed behavioral observations for
the CLI, collector signing API, units, and storage limits. Native application
effect ledgers still lack per-dependency attribution, so this surface consumes
isolated-package collector observations.
security::bpet::evolution_risk_scorer::compute_risk_score takes a
FeatureVector of four normalized features (each in [0.0, 1.0]):
drift— distributional drift of the observed behaviorregime_shift— probability that behavior has crossed a regime boundaryhazard— current hazard rate from the extension's reliability baselineprovenance— confidence in the publisher / supply-chain provenance inputs
It applies a WeightingPolicy (also four-dimensional, validated to be
finite, non-negative, and sum-to-one within SUM_TOLERANCE) and emits
an ExplanationVector plus a ConfidenceInterval. Companion modules
extend the surface: phenotype_extractor (lifts evidence into
ExtractedFeature::{Known, Partial, Unknown} annotations), dgis_fusion
(folds security::dgis::update_copilot::TopologyRiskMetrics into the
scorer's inputs), trust_surface_integration (mutates trust cards via
trust_card_mutation_from_guidance and produces adversary-posterior
updates), and federation::bpet_atc_bridge (carries the scorer output
into the federation layer).
migration::bpet_migration_gate is an independent admission gate that
operates on TrajectorySnapshot/TrajectoryDelta inputs via
evaluate_admission and evaluate_rollout_health; a sufficient delta
versus the prior version raises the migration's required assurance level
before the gate will admit it.
VEF execution receipts
vef::* produces Verifiable Execution Fingerprints: a hash-linked
chain of signed receipts over execution segments. The chain is built from
ReceiptChainEntry items, each containing:
index: monotonic position in the chain.prev_chain_hash: the prior entry's chain hash, so any tampering breaks every downstream entry.receipt_hash: canonical hash over the entry'sExecutionReceipt(which carries the segment's inputs, outputs, and worker identity).chain_hash:H(prev_chain_hash || receipt_hash)for this entry.appended_at_millis,trace_id: for correlation.
ReceiptCheckpoint entries periodically commit the chain head plus an
inclusive index range so partial verification is cheap. Verification
(vef::receipt_chain::verify_integrity, verify_entries_and_checkpoints)
recomputes each chain_hash and matches it against the recorded value;
vef::proof_verifier then checks the per-receipt signatures.
sdk/verifier consumes these so an external party can validate a
sequence of segments without trusting the producer.
Time-travel replay engine
replay::time_travel_engine is a library API that no CLI command calls
yet. incident replay re-derives recorded bundle integrity by default;
its --execute path uses ops::native_replay and the native execution
orchestrator. The separate library engine captures a WorkflowTrace: the sequence of
steps, their inputs and outputs, the environment snapshot at the start of
the workflow, schema version metadata, and the side-effect declarations
required to reproduce them. Its replay re-executes the trace against the
same schema version and produces a ReplayResult whose verdict is one
of:
ReplayVerdict::Identical: every step's output and side-effects matched the captured values.ReplayVerdict::Diverged(count):countsteps diverged; details appear asDivergenceentries indexed in the result.
Each Divergence carries a typed DivergenceKind:
OutputMismatch: output bytes differ.SideEffectMismatch: declared/observed side-effects differ.FullMismatch: both output and side-effects differ.ClockDrift { expected_ns, actual_ns, drift_ns, tolerance_ns }: timestamp drift exceeded the tolerance window.
Bundle export is fsync-backed and uses an atomic-rename TempFileGuard, so crash recovery leaves either the prior bundle or the new one; never a torn write.
Incident Bundle Anatomy
A .fnbundle produced by franken-node incident bundle is a single
canonical JSON artifact (keys sorted, no floats) built from an incident
evidence package (.franken-node/state/incidents/<id>/evidence.v1.json).
Captured source packages retain their evidence fields at the top level and add
source_signature. This Ed25519 seal covers the complete canonical source:
events, metadata, detector, severity, trace, evidence references, and run receipt
and host-effect chain bindings. Its signature domain is separate from bundle
signatures. The configured receipt-signing authority authenticates the source
before export; --receipt-signing-key selects the exported bundle signer and
does not replace that source authority. Missing or invalid source signatures,
duplicate JSON keys, and unknown source-schema fields are rejected.
Recorded native runs also bind a complete receipt_snapshot to their run
metadata and engine ledger under a product record signature. Capture verifies
this record and the engine's session identity, signature, and effect chain.
Editing both advertised receipt hashes or substituting receipt metadata cannot
satisfy that binding. Existing unsigned run records must be recreated by a
fresh run before they can supply authenticated runtime capture.
Native execution failures with a recovered dispatch report pass through the same
SSRF quarantine, Sentinel enforcement, receipt persistence, and automatic incident
capture as successful executions. Throwing after a denied effect cannot skip those
controls. The receipt commits the original execution_failure before its identity
and hash are derived, and the durable run decision records the failed attempt as
Deny. A recovered ledger retains its independently verified engine identity and
can be captured and bundled later; an unavailable finalized ledger remains absent.
For these failures, run --json preserves the
franken-node/run-failure-effect-evidence/v2 envelope and its error,
captured_output, and host_effect_ledger, adding receipt, receipt_path, and
dispatch. The command still exits nonzero. If receipt persistence itself fails,
the envelope retains the original failure and console and reports
persistence_error, without inventing a persisted receipt. --console-only
continues to emit the guest streams without receipt or incident notices.
Native timeouts also retain an execution receipt. Its interruption_evidence
binds the parent-observed write-ahead journal before receipt identity and hashing.
The signed durable Deny decision includes that journal, the complete
run_receipt_snapshot, and the authenticated session identity context, so the
observation remains inspectable if the separate receipt file becomes unavailable.
The decision signs the parent's observation; session identity alone does not
authenticate a finalized engine outcome. An unmatched admission remains
interrupted_indeterminate, a provider-return marker remains provider_returned,
and replay_certified stays false, including when worker cleanup is unproven.
Timeout JSON retains the franken-node/native-effect-interruption-evidence/v1
fields and adds error, receipt, receipt_path, and dispatch.
guest_output_available: false makes clear that the interrupted worker did not
supply its final console response. Receipt-storage failure retains the journal
and original timeout error with a separate persistence_error. Interrupted runs
remain nonzero failures, produce no fabricated finalized host-effect ledger or
automatic replay capture, and remain visible as source errors in incident
coverage. incident capture --from-run explicitly refuses to certify them.
An explicit --evidence-path can still import operator-authored evidence;
the export reports source_authenticated: false. Captured-store files and inputs
that contain source signatures or runtime-run claims still require source
authentication, including when an explicit path is supplied. Listing a corrupted
captured source reports it as invalid, and repeated capture authenticates an
existing source before returning already_captured.
ops incident-coverage measures retained durable run decisions and discoverable
run records, derives incident eligibility from authenticated security outcomes,
and matches captured sources to their signed bundles. Missing files associated
with retained decisions remain visible. Clean successful Node/Bun runs are
excluded from native-ledger checks only by their authenticated durable decisions,
never by an editable receipt runtime label; JSON counts those exclusions separately.
A requested coverage floor fails when
the measured population is empty, unreadable, or unauthenticated. Its reported
population_scope does not establish historical completeness after deletion
or rollback of the local inventory and trust material.
Durable decisions use chain-v1:<hex> Ed25519 signatures. A distinct signing
domain binds the entry contents and its exact predecessor before the append
transaction commits. Deleting a middle entry and editing the next link therefore
invalidates the signature, even when the remaining hash links match. The
in-memory ledger refuses to fill in or normalize a predecessor covered by this
signature. Standalone content signatures retain their existing contract.
verify transparency-log --public-key requires an empty predecessor at genesis
and authenticated links throughout the retained chain. JSON reports
chain_links_authenticated, unbound_signature_entries, and head_hash.
An older log with valid standalone signatures has signatures_verified: true
but status: "unproven" and exits nonzero; coverage refuses to treat that log as
an authenticated inventory. Existing rows are preserved and never silently
re-signed into proof of a history that was not authenticated at append time.
A genuine older prefix still has valid signatures. To detect that rollback,
retain an authenticated head_hash separately and supply it later with
--expected-head-hash. A matching authenticated chain reports
checkpoint_verified: true; a different or truncated head reports a checkpoint
error and exits nonzero. The checkpoint establishes the expected retained head,
not freshness beyond the operator's trusted observation. Without a checkpoint,
valid establishes the retained chain only. The verifier rejects logs exceeding
its 10,000-entry limit instead of silently checking a truncated window.
The exported bundle's fields (tools::replay_bundle::ReplayBundle):
| Field | Purpose |
|---|---|
bundle_id, incident_id, created_at |
Stable identifiers; bundle_id and created_at are derived deterministically from the timeline. |
policy_version |
The policy version recorded with the evidence. |
timeline |
Ordered TimelineEvents: sequence_number, timestamp, event_type, payload, causal_parent. |
initial_state_snapshot |
The captured state at the start of the window. |
manifest |
Event count, time span, chunk count, compressed size, and decision_sequence_hash over timeline + initial state + policy version. |
chunks |
Gzip-sized BundleChunk segments of the timeline. |
evidence_refs, trust_artifact_refs |
Relative pointers to the evidence behind each event (omitted when empty). |
integrity_hash |
SHA-256("replay_bundle_hash_v1:" ‖ canonical JSON of every field above). |
signature |
Ed25519 over "replay_bundle_sig_v1:" ‖ u64_le(len) ‖ integrity_hash, with the signer's public key, key id, source and trust scope. |
incident replay recomputes the integrity hash, verifies the signature
against the operator-supplied trust anchor, and re-derives the
decision-sequence hash from the recorded timeline. This default mode reports
replay_kind: recorded_trace. With --execute, a bundle carrying authenticated
native replay inputs also re-executes the captured guest and reports its
separate execution_result; the supported comparison scopes are described in
Re-execute captured native JavaScript.
Independent verification: frankenengine_verifier_sdk::incident_bundle::verify_incident_bundle
re-derives the integrity hash and the decision-sequence hash from the raw
JSON, rejects unknown (unsigned) fields, and verifies the Ed25519 signature
strictly under a public key the verifier supplies. The key embedded in the
bundle is accepted only if it equals that anchor. Conformance:
sdk/verifier/tests/cli_incident_bundle.rs (a real CLI-produced bundle) and
incident_bundle_integrity_conformance_sdk_independently_verifies_product_bundle.
Counterfactual mode (incident counterfactual --policy <p>) re-scores the
recorded decision trace under a different policy's thresholds with the
labelled synthetic executor. It does not execute the program again, and
the bundle itself is unchanged.
Epoch Transition Barrier
The epoch transition barrier primitive
(control_plane::epoch_transition_barrier) drains in-flight work when
a policy change, key rotation, schema bump, or fleet release advances.
The default CLI fleet path does not drive this as a live multi-node
plane (live_control_plane=false).
The barrier moves through ordered BarrierPhase states; each participant
acknowledges the phase with a DrainAck. The barrier carries:
BarrierId: opaque identifier for the in-flight barrier.ParticipantId: per-node identity expected to drain and acknowledge.SCHEMA_VERSION = "eb-v1.0": pinned schema for the barrier payload.DEFAULT_BARRIER_TIMEOUT_MSandDEFAULT_DRAIN_TIMEOUT_MS: hard upper bounds on how long the barrier waits before aborting.- An
event_codesmodule exporting stable codes for every phase transition. - An
error_codesmodule exporting stable codes for every refusal or abort condition.
If the timeout fires before all participants acknowledge, the barrier
emits an AbortReason and refuses to advance the epoch. The runtime
falls back to the prior epoch and the operator gets a structured event
naming the participant that failed to drain. The barrier fails closed: advancement only happens when every
participant acknowledges in time.
This is the mechanism that makes "policy rollout" a transactional operation across the fleet rather than a best-effort broadcast.
Anti-Entropy Reconciliation
Replicas in a fleet zone may temporarily disagree on observable state
(network partition, slow follower, missed marker). runtime::anti_entropy
runs a deterministic reconciliation cycle that re-converges replicas
without violating the linearizability of their decision logs.
Each cycle emits stable event codes from
runtime::anti_entropy::event_codes so a SIEM filter can pin them
without parsing message text:
| Event | Code | Meaning |
|---|---|---|
| Cycle started | FN-AE-001 |
A new reconciliation cycle began |
| Delta computed | FN-AE-002 |
The deltas between local and peer state vectors were computed |
| Record accepted | FN-AE-003 |
A peer record was accepted into the local state |
| Record rejected | FN-AE-004 |
A peer record was refused (schema, signature, or epoch violation) |
| Cycle completed | FN-AE-005 |
The cycle reached a stable terminating state |
| Fork detected | FN-AE-006 |
Reconciliation surfaced a fork; emits a divergence receipt |
| Cycle cancelled | FN-AE-007 |
The cycle was cancelled by an operator or supervisor |
| Replay idempotent | FN-AE-008 |
A replayed cycle had no observable effect (idempotency proof) |
Refusal conditions have matching stable error codes
(ERR_AE_INVALID_CONFIG, ERR_AE_EPOCH_VIOLATION, ERR_AE_PROOF_INVALID,
ERR_AE_FORK_DETECTED, ERR_AE_CANCELLED, ERR_AE_BATCH_EXCEEDED).
The reconciler is benchmarked under anti_entropy_insert_bench so
regressions surface in CI.
Key-Role Separation
control_plane::key_role_separation enforces that the same Ed25519 key
cannot serve two distinct roles in the protocol. The KeyRole enum has
four variants — Signing (authenticate control-plane messages and
attestations), Encryption (protect confidential payloads in transit
and at rest), Issuance (create delegation tokens and authority
certificates), and Attestation (sign operator attestation payloads),
each carrying a fixed 2-byte role tag. A KeyRoleBinding records which
key fingerprint maps to which role; a KeyRoleRegistry rejects any
operation that attempts to mix roles.
Operational consequences:
- A key compromise in one role does not silently compromise another.
Stealing an
Encryptionkey cannot be used to issueSigningartifacts like decision receipts. - Role binding is enforced in constant time so a probe cannot leak whether a given key is "almost" the wrong role.
- The registry emits a typed
KeyRoleSeparationErroron every refused binding and aKeyRoleEventfor every accepted one, so the audit trail is complete.
End-to-end coverage lives in tests/e2e_key_role_separation_registry.rs
and tests/security/threshold_signature_verification.rs.
Decision Receipt Anatomy
A signed decision receipt is the smallest atomic unit of accountability
in franken-node. Anything an operator does (release a quarantine,
quarantine an artifact, revoke a publisher, promote a counterfactual,
restart a proof worker) produces one. The unsigned payload is the
Receipt struct (security::decision_receipt::Receipt); the on-the-
wire artifact is SignedReceipt, which flattens Receipt and adds the
signer / chain / signature triple.
Receipt fields:
| Field | Purpose |
|---|---|
receipt_id |
Unique stable identifier for this receipt |
action_name |
The action that was decided (e.g. fleet.release, trust.revoke) |
actor_identity |
Identity that authorized the action |
timestamp |
RFC 3339 issuance time; monotonicity validated against the prior receipt |
signature_version |
Pinned signature scheme/version committed into the canonical payload |
nonce |
Per-receipt nonce; defeats receipt-reuse attacks |
audience |
Audience binding; defeats cross-context receipt abuse |
input_hash |
Canonical hash over the decision's inputs |
output_hash |
Canonical hash over the decision's outputs |
decision |
Outcome enum: Approved, Denied, or Escalated |
rationale |
Human-readable explanation for the outcome |
evidence_refs |
List of opaque string pointers (path-like or registry-defined IDs) backing this decision |
policy_rule_chain |
Ordered list of policy rule IDs that produced this outcome |
confidence |
f64 confidence score (canonicalized so NaN/Inf never appear) |
rollback_command |
The exact command an operator would run to reverse the decision |
previous_receipt_hash |
Hash of the prior receipt in the chain (None for the chain root) |
SignedReceipt adds:
| Field | Purpose |
|---|---|
signer_key_id |
Stable identifier for the key that signed this receipt |
chain_hash |
`H(previous_chain_hash |
signature |
Ed25519 signature over the canonical preimage, verified in constant time |
The canonical preimage is domain-separated and length-prefixed, so two
receipts can never share a signature input by accident. Receipts are
written atomically (TempFileGuard + fsync of the containing directory),
so a crash leaves either the prior receipt or the new one; never a
torn write. The hash chain (previous_receipt_hash + chain_hash)
binds the receipt log into a tamper-evident sequence; an auditor only
needs the chain head and the per-receipt signatures to verify the whole
history.
Determinism and Replay Architecture
Deterministic replay only works if every input is captured and every non-deterministic source is intercepted. franken-node enforces this through four overlapping layers:
- Schema version registry.
schema_versions.rsenumerates every serialized artifact (trust card snapshot, decision receipt, evidence entry, replay capsule, migration bundle, audience token, MMR proof, threshold-sig payload, fleet decision receipt) with an integer schema version. Schemas are immutable once shipped; migrations are explicit version bumps with upgrade and downgrade helpers. - Canonical serialization. Every artifact has a single canonical byte form. Field order is fixed, integers are little-endian, variable-length fields are length-prefixed, and a domain separator prefixes the entire payload. Signatures are computed over the canonical bytes; verification recomputes them from the parsed structure.
- Captured environment.
WorkflowTrace::environment_snapshotincludes the policy profile, active feature flags, deterministic clock discipline policy, and the resolved configuration hash. Replay refuses to run if any of these has drifted (or, in counterfactual mode, the policy delta is recorded explicitly as the experimental change). - Side-effect declarations. Each step declares the set of side-effects it intends to perform (file write, network call, registry query). The runtime records the actual side-effects observed. Replay compares declared vs. observed and refuses to run a step whose side-effect set has drifted.
The four layers compose: a replay bundle that includes a captured trace, an environment snapshot, schema versions, and side-effect declarations can be replayed years later, on a different machine, with bit-exact results, provided the schema versions are still registered.
Threat Model
What franken-node defends against, mapped to the primitive that enforces the defense:
| Threat class | Example | Defense |
|---|---|---|
| Supply-chain forgery | Adversary publishes a same-named artifact with a tampered binary | Registry requires Ed25519 signature; registry verify recomputes the artifact hash and verifies the signature in constant time |
| Dependency poisoning | Compromised transitive npm dep ships a malicious payload | OSV refresh on trust sync; revocation freshness gates fail closed for risky actions when the frontier is stale |
| Typosquatting | Adversary publishes lodahs, @evil/express |
trust scan compares each dependency against a pinned list of popular npm names (one edit away, scope or separator confusion, look-alike characters) and raises a match to High risk with the reason on the card. run under strict refuses High/Critical dependencies (high_risk); balanced admits them with a preflight warning; legacy-risky ignores risk. The seed list is small and curated, so a typosquat of an unlisted package is not caught. |
| Revocation race | Adversary issues a sensitive action between revocation publication and local cache refresh | risky_requires_fresh_revocation = true + dangerous_requires_fresh_revocation = true block actions whose tier requires fresher state |
| State divergence / fork attack | Adversary feeds two nodes inconsistent control-plane state | Fork detection state vectors; rollback proofs; RollbackDetected and Forked outcomes gate subsequent decisions |
| Replay attack on tokens | Adversary re-uses a captured audience token | Bounded nonce window + now >= expires_at fail-closed + constant-time signature verification |
| Timing side-channel | Adversary extracts a signing key by measuring verification time | security::constant_time::ct_eq{,_bytes} (backed by subtle) on every signature, hash, MAC, content-hash, trace-id, action-id check |
| Hash-collision / canonicalization mismatch | Adversary crafts two inputs that pipe-collide under a delimiter-based hash | Domain separators (b"<module>_<function>_vN:") + length-prefixed variable-length fields throughout |
| Counter overflow | Adversary forces a u64 counter to wrap and bypass a check | saturating_add / saturating_sub on every counter, sequence, epoch, and timestamp |
| Parser-bomb DoS | Adversary submits an artifact that explodes on parse | bounded_read{,_to_string} enforce per-file size caps before parse; capacity-critical Vec paths use push_bounded |
| Auth-failure memory DoS | Adversary triggers per-source-IP tracking growth | AuthFailureLimiter caps source-IP cardinality |
| Torn writes on crash | Power loss between rename and fsync corrupts durable state | TempFileGuard atomic-rename + directory fsync on every durable write |
| Trajectory gaming | Adversary slowly drifts a benign extension into a malicious one | Camouflage assessment scores trajectory deltas; ATC reciprocity catches asymmetric contribution; BPET evolution risk gates rollouts |
| Schema downgrade | Adversary submits an old-schema artifact to bypass a new check | Schema version pinning in schema_versions.rs; epoch barriers reject artifacts outside the validity window |
| Verifier collusion | Producer signs its own claims and asks you to trust them | frankenengine-verifier-sdk runs outside the producing runtime; receipts are linked, signed, and independently verifiable |
| Memory unsafety | Use-after-free, buffer overflow | #![forbid(unsafe_code)] lint forbidance + Rust borrow checker |
| CAS poisoning / EffectReceipt splice | Adversary rewrites content-addressed bytes, back-dates an effect, or splices a forged receipt into a host-effect chain | CAS read-time hash verification plus domain-separated, length-prefixed EffectReceipt hashes and chain-link recomputation; regression anchors: crates/franken-node/tests/cas_effect_receipt_conformance.rs and crates/franken-node/src/runtime/effect_receipt.rs |
| Declassification-authority abuse | Adversary forges, over-scopes, expires, or replays a declassification receipt at the wrong sink or epoch | Declassification receipts bind schema, sink policy, forbidden label set, actor, purpose, epoch, revocation freshness, and canonical receipt id; regression anchors: crates/franken-node/src/security/lineage_tracker.rs and tests/security/exfiltration_sentinel_scenarios.rs |
| MCP mutation abuse | Agent or sub-agent escalates scope, delegates beyond the parent token, omits rollback evidence, or replays a stale mutation session | Mutating MCP tools require audience-bound capability chains, attenuated delegation, rollback commands, signed action receipts, and session digest verification; regression anchors: crates/franken-node/src/api/mcp.rs and crates/franken-node/tests/mcp_control_surface_contract.rs |
| LTV witness collusion / crypto-suite downgrade | Witnesses collude to re-attest stale evidence or submit an artifact under a weaker crypto_suite discriminator |
TNR release gates must require suite-discriminator binding, witness independence checks, and fail-closed re-attestation evidence before LTV claims ship; current related regression anchors: crates/franken-node/tests/threshold_sig_quorum_metamorphic.rs and docs/specs/crypto_trait_abstraction.md |
| Conformal calibration and Sentinel signal poisoning | Adversary contaminates the Phase-0 corpus or correlated Sentinel signals to widen or narrow BPET coverage | Signed calibration artifacts, canonical corpus records, verifier recomputation, poisoning/Sybil policy thresholds, and Sentinel quarantine interplay; regression anchors: tests/security/bpet_calibration_benchmark.rs, docs/policy/signal_poisoning_sybil_defense.md, and docs/policy/risk_signal_poisoning_sybil.md |
| Fleet-log equivocation / quorum replay | Partitioned validators race catch-up, replay quorum certificates, or double-count duplicate attestations | Canonical action hashing, distinct-validator quorum accounting, equivocation fault receipts, decision-receipt tamper rejection, and threshold-signature replay tests; regression anchors: crates/franken-node/src/api/fleet_quarantine.rs, tests/security/threshold_signature_verification.rs, and crates/franken-node/tests/threshold_sig_quorum_metamorphic.rs |
Out of scope: explicit non-goals so operators know what to layer on top:
- Network-level DoS protection. franken-node has internal bulkheads and bounded resource governance, but it is not a load balancer or WAF.
- Filesystem-level mandatory access control. SELinux, AppArmor, or equivalent should remain in place.
- Hardware-attested execution. SGX/TDX integration is a separate layer; the verifier SDK is designed to compose with hardware attestation but does not require it.
- Source-code provenance for the application itself. franken-node validates extension provenance; application source provenance is the caller's responsibility.
Schema Versioning Policy
schema_versions.rs is the single authoritative registry of every
serialized artifact's schema version and the upgrade/downgrade helpers
between versions. The policy:
- Immutability after release. Once a schema version ships, its canonical byte form is frozen. Bug fixes that change byte output are schema bumps, not patches.
- Explicit migrations. Every bump requires registering an upgrade
helper from
vN → vN+1and (where feasible) a downgrade helper fromvN+1 → vN. Verifiers that pin an older version can still consume newer artifacts when the downgrade path is non-lossy; when it is lossy, the verifier explicitly refuses. - Refusal on unknown. A bundle or capsule whose schema version is not registered fails verification. There is no "best effort" deserialization.
- Golden coverage. Every shipped version has a golden test in
tests/golden/whose bytes lock the canonical encoding. Changing the bytes without a version bump fails CI. - Cross-artifact consistency. When two artifacts must agree (e.g. a decision receipt references a trust card snapshot), both must use schema versions that the runtime can reconcile. The validity window check on epochs enforces this for control-plane artifacts.
Pinned versions, immutability, golden bytes, and refusal on unknown together make replay deterministic across time and across machines.
Cryptographic Primitives
| Purpose | Primitive | Source crate |
|---|---|---|
| Asymmetric signing / verification | Ed25519 | ed25519-dalek |
| Constant-time comparison | subtle::ConstantTimeEq |
subtle (via security::constant_time) |
| General hashing | SHA-256 | sha2 |
| Optional fast hashing | BLAKE3 | blake3 (feature-gated) |
| Keyed hashing / snapshots | HMAC-SHA-256 | hmac |
| Key derivation | HKDF-SHA-256 | hkdf |
| Random nonces / IDs | OS RNG | rand (cryptographic backend) |
| Secret zeroization | Zeroize derive |
zeroize |
| Canonical CBOR (optional) | ciborium |
feature-gated |
| Replay-bundle compression | Gzip via DEFLATE | flate2 (feature-gated) |
Every signature and every keyed-hash input begins with a domain
separator: a literal byte string of the form
b"<module>_<operation>_v<version>:", so a valid signature for one
protocol context can never be reused in another.
State Layout
.franken-node/ is created by franken-node init (unless --no-state
is supplied). Override the state location with --state-dir, or
FRANKEN_NODE_FLEET_STATE_DIR for the fleet subtree specifically. The
bootstrap layout is:
| Path | Purpose |
|---|---|
.franken-node/state/ |
State root |
.franken-node/state/incidents/<incident-id>/evidence.v1.json |
Signed captured incident source, authenticated before listing or bundle export |
.franken-node/state/run-ledgers/<day>/<receipt-id>.json |
Product-signed run metadata, complete execution receipt snapshot, and engine-signed host-effect ledger |
.franken-node/state/execution-receipts/ |
Transient execution receipts; excluded from version control by the generated .gitignore |
.franken-node/state/fleet/ |
Durable fleet transport state (signed decision receipts, per-node convergence) |
.franken-node/state/registry/ |
Local registry artifact store root |
.franken-node/state/registry/artifacts/ |
Active signed extension artifacts |
.franken-node/state/registry/archive/ |
Archived artifacts retained after registry gc |
.franken-node/state/migrations/ |
Migration audit, rewrite, and validate outputs |
.franken-node/state/trust-card-registry.v1.db |
Durable trust-card registry store (WAL frankensqlite; the legacy .v1.json pair is a one-time import source only) |
.franken-node/safe-mode/state.json |
Unsigned JSON safe-mode controller; an active scope blocks new runs and interrupts running native sessions with admitted entrypoints in this directory or its descendants. --state-dir can address a standalone controller; only the conventional ancestor paths govern run. |
.franken-node/keys/ |
Signing key material; excluded from version control by the generated .gitignore |
.franken-node/keys/receipt-signing.key |
Default Ed25519 seed (mode 0600) for decision receipts, captured incident sources, run-ledger records, incident bundles, close-condition receipts and evidence-ledger entries, used when neither --receipt-signing-key, FRANKEN_NODE_SECURITY_DECISION_RECEIPT_SIGNING_KEY_PATH nor security.decision_receipt_signing_key_path is set |
.franken-node/keys/receipt-signing.pub |
Matching public key (hex), for verify transparency-log --public-key and incident replay --trusted-public-key |
.franken-node/state/evidence-ledger.db |
Durable evidence ledger: one signed, hash-chained entry per run (WAL frankensqlite) |
.franken-node/keys/remotecap-signing.key |
RemoteCap signing key (mode 0600), used when FRANKEN_NODE_REMOTECAP_KEY is unset |
.franken-node/remotecap/trust-scan-token.json |
Default trust-scan egress token (mode 0600, git-ignored), used when FRANKEN_NODE_TRUST_SCAN_REMOTECAP_TOKEN is unset |
Additional subtrees are materialized lazily by individual subsystems
(.franken-node/safe-mode/state.json unsigned JSON, proof pipeline
snapshots, evidence ledger, validation-broker receipt files, cleanup
audit) as those subsystems are exercised; the bootstrap above is the
minimum layout that franken-node init creates up-front.
Native-runtime evidence authority is deliberately outside this project tree,
because the project directory is also the guest filesystem sandbox root.
<user-state> is XDG_STATE_HOME when set, otherwise
$HOME/.local/state (LOCALAPPDATA is also recognized on Windows):
| Path | Purpose |
|---|---|
<user-state>/franken-node/runtime-evidence/keys/product-root.key |
Owner-only persistent product root; never serialized into the execution child |
<user-state>/franken-node/runtime-evidence/identity-captures/<verification-key>.json |
Product-root-signed public identity capture for one short-lived native-engine evidence key |
These directories are created lazily with owner-only permissions. Native
startup fails closed if the resolved user-state directory is inside the guest
project root. A process-spawn-enabled Bubblewrap worker additionally masks the
complete runtime-evidence directory with an empty tmpfs, so an allowed child
utility cannot read the parent-owned root through the read-only host-root bind.
Every durable write goes through an atomic-rename + directory-fsync
TempFileGuard; readers take an fs2 advisory lock when consistency
across multiple writers is required (see lib.rs::lock_utils).
Environment Variables
franken-node reads configuration in this precedence: CLI flags → environment
variables → the selected profile block in franken_node.toml → base values in
that file (resolved by --config or default discovery) → built-in safe
defaults. Environment variables follow the FRANKEN_NODE_<SECTION>_<KEY>
convention. The most common:
| Variable | Equivalent config | Notes |
|---|---|---|
FRANKEN_NODE_PROFILE |
profile |
strict, balanced, or legacy-risky |
FRANKEN_NODE_RUNTIME_PREFERRED |
runtime.preferred |
auto, node, bun, or franken-engine |
FRANKEN_NODE_RUNTIME_ALLOW_PROCESS_SHAPE |
runtime.allow_process_shape |
Explicit true/false for static process metadata reads in the native entrypoint; omitted values retain the profile default; replay capture retains the bounded grant and supplied arguments |
FRANKEN_NODE_RUNTIME_MAX_INSTRUCTIONS |
runtime.max_instructions |
Instruction budget for one run (> 0); replaces the profile default |
FRANKEN_NODE_RUNTIME_MAX_REGISTERS |
runtime.max_registers |
Registers per call frame on both native lanes (1–65,536); replaces profile defaults |
FRANKEN_NODE_RUNTIME_MAX_CALL_DEPTH |
runtime.max_call_depth |
Guest call depth on both native lanes (1–10,000); replaces the profile default |
FRANKEN_NODE_RUNTIME_MAX_HEAP_OBJECTS |
runtime.max_heap_objects |
Positive allocation ceiling on both native lanes; not a live-object count |
FRANKEN_NODE_RUNTIME_MAX_TOTAL_MEMORY_BYTES |
runtime.max_total_memory_bytes |
Positive engine-accounted memory ceiling in bytes; excludes console text and is not RSS |
FRANKEN_NODE_RUNTIME_MAX_CONSOLE_ENTRIES |
runtime.max_console_entries |
Positive console-entry ceiling on both lanes; overflow fails and the fixed byte ceiling still applies |
FRANKEN_NODE_RUNTIME_MAX_PARSE_SOURCE_BYTES |
runtime.max_parse_source_bytes |
Source bytes per entry/required/imported JavaScript module (1–2,097,152); replaces the profile default |
FRANKEN_NODE_RUNTIME_MAX_PARSE_TOKENS |
runtime.max_parse_tokens |
Tokens per entry/required/imported JavaScript module (1–131,072); replaces the profile default |
FRANKEN_NODE_RUNTIME_MODULE_CONDITIONS |
runtime.module_conditions |
JSON string array, for example ["development","react-server"]; additional package exports/imports conditions. [] clears configured conditions. |
FRANKEN_NODE_ENGINE_BINARY_PATH |
engine.binary_path |
Override the resolved franken_engine binary path |
FRANKEN_NODE_COMPATIBILITY_MODE |
compatibility.mode |
API compatibility mode |
FRANKEN_NODE_COMPATIBILITY_EMIT_DIVERGENCE_RECEIPTS |
compatibility.emit_divergence_receipts |
true/false |
FRANKEN_NODE_REPLAY_BUNDLE_VERSION |
replay.bundle_version |
Pin replay bundle format |
FRANKEN_NODE_REPLAY_MAX_REPLAY_CAPSULE_FRESHNESS_SECS |
replay.max_replay_capsule_freshness_secs |
Freshness window for replay capsules |
FRANKEN_NODE_FLEET_STATE_DIR |
fleet.state_dir |
Override the fleet transport state root |
FRANKEN_NODE_FLEET_NODE_ID |
fleet.node_id |
Default fleet agent node ID |
FRANKEN_NODE_FLEET_POLL_INTERVAL_SECONDS |
(none) | Default fleet agent poll interval |
FRANKEN_NODE_FLEET_CONVERGENCE_TIMEOUT_SECONDS |
fleet.convergence_timeout_seconds |
Fleet release / reconcile timeout |
FRANKEN_NODE_DOCTOR_POLICY_ACTIVATION_INPUT |
doctor --policy-activation-input |
Fallback path to policy-activation snapshot JSON (not a live probe); the CLI flag takes precedence |
FRANKEN_NODE_REGISTRY_REQUIRE_SIGNATURES |
registry.require_signatures |
Fail-closed if unset |
FRANKEN_NODE_REGISTRY_REQUIRE_PROVENANCE |
registry.require_provenance |
Fail-closed if unset |
FRANKEN_NODE_REGISTRY_MINIMUM_ASSURANCE_LEVEL |
registry.minimum_assurance_level |
1-5 |
FRANKEN_NODE_REMOTE_IDEMPOTENCY_TTL_SECS |
remote.idempotency_ttl_secs |
Remote-cap idempotency window |
FRANKEN_NODE_OBSERVABILITY_NAMESPACE |
observability.namespace |
Metrics namespace |
FRANKEN_NODE_OBSERVABILITY_EMIT_STRUCTURED_AUDIT_EVENTS |
observability.emit_structured_audit_events |
true/false |
FRANKEN_NODE_MIGRATION_AUTOFIX |
migration.autofix |
Autofix toggle |
FRANKEN_NODE_MIGRATION_REQUIRE_LOCKSTEP_VALIDATION |
migration.require_lockstep_validation |
Doctor WARN when false; migrate validate still does not run verify lockstep |
FRANKEN_NODE_SECURITY_MAX_MERGE_DECISIONS |
(security cap) | Bounded decision history |
FRANKEN_NODE_BENCHMARK_MIN_THROUGHPUT_OPS / ..._MAX_LATENCY_MS / ..._MIN_AGGREGATE_SCORE |
bench thresholds | Bench gate parameters |
RUST_LOG |
(none) | tracing-subscriber filter expression for diagnostic logging |
Run franken-node ops config-audit --json to see the resolved
configuration with all environment-variable overlays applied.
Structured Logging and Observability
franken-node has two distinct logging surfaces. tracing-subscriber
diagnostics are filtered with the standard RUST_LOG syntax for
internal debugging; --structured-logs-jsonl is a stable,
command-specific event stream intended for SIEM ingestion.
RUST_LOG=info,frankenengine_node=debug \
franken-node init --profile balanced --scan \
--structured-logs-jsonl --trace-id rollout-2026-05-16-canary
Each --structured-logs-jsonl line is a single canonical JSON object
with a fixed shape per command, carrying a stable event_code:
{
"timestamp": "2026-05-16T14:22:31.118Z",
"level": "info",
"event_code": "INIT-001",
"message": "init command started",
"trace_id": "rollout-2026-05-16-canary",
"span_id": "init-bootstrap",
"surface": "CLI-INIT"
}
Event codes are command-prefixed (INIT-001/INIT-002/INIT-003,
RUN-001/RUN-002/RUN-003/RUN-004, DR-* for doctor,
FLEET-005, DR-TRUST-004, DR-MIGRATE-005, …) so a downstream
filter can pin the codes it cares about without parsing the message
text. The surface field identifies the command family (e.g.
CLI-INIT, CLI-RUN, CLI-DOCTOR).
franken-node ops metrics --format prometheus exports operational
signals under the namespace franken_node_* (configurable via
[observability].namespace). The metric surface is stable; consumers
can pin directly against the names. Current names emitted by the CLI:
| Metric | Type | Labels | Surface |
|---|---|---|---|
franken_node_health_pass |
gauge | — | 1 when this CLI ops health-check passed (compiled git SHA plus a local ledger/receipt file); not per-surface daemon liveness |
franken_node_process_uptime_seconds |
gauge | — | Uptime of this CLI process (ops metrics / ops health-check); not a long-running node daemon |
franken_node_active_session_count |
gauge | — | Count of files under <project>/.franken-node/state/sessions (same source as ops health-check; not a live SessionManager) |
franken_node_build_info |
gauge | version, git_sha |
Always 1; labels carry version info |
franken_node_last_successful_evidence_ledger_flush_timestamp_seconds |
gauge | — | Last successful evidence ledger flush (epoch seconds) |
franken_node_evidence_ledger_spill_entries |
counter | — | Entries spilled from the evidence ledger to durable storage |
franken_node_incident_evidence_files |
gauge | — | Incident evidence files on disk |
franken_node_execution_receipts |
counter | — | Execution receipts emitted |
franken_node_fleet_active_quarantines |
counter | zone |
Active quarantines per fleet zone |
franken_node_fleet_node_records |
gauge | — | Tracked fleet node records |
franken_node_revocation_filter_entries |
gauge | — | Current entries in the cuckoo revocation filter |
The evidence ledger is a stronger consistency surface than metrics:
every signed decision lives there with witness traces and is queryable
through ops/evidence_index. Metrics are for dashboards; the ledger is
for audits.
Output Contracts
Every command that accepts --json emits a stable, schema-versioned
shape. Downstream automation can rely on these shapes; CI integration
tests assert them against goldens.
Decision receipt (fleet release, trust revoke, trust quarantine,
incident counterfactual --promote). The on-the-wire shape is
SignedReceipt, which flattens Receipt:
{
"receipt_id": "rec-2026-0042",
"action_name": "fleet.release",
"actor_identity": "ops-1",
"timestamp": "2026-05-16T14:22:31.118Z",
"signature_version": "ed25519-v1",
"nonce": "9d4f3a…",
"audience": "fleet:prod-us-east",
"input_hash": "sha256:abc…",
"output_hash": "sha256:def…",
"decision": "approved",
"rationale": "incident INC-2026-0007 remediated and verified",
"evidence_refs": ["INC-2026-0007/timeline.jsonl", "INC-2026-0007/quarantine-receipt.json"],
"policy_rule_chain": ["fleet.release.requires_remediation", "fleet.release.requires_operator"],
"confidence": 0.99,
"rollback_command": "franken-node trust quarantine --artifact …",
"previous_receipt_hash": "sha256:1234…",
"signer_key_id": "operator-2026-05",
"chain_hash": "sha256:5678…",
"signature": "<base64-ed25519>"
}
Trust card (trust card, trust-card export). The on-the-wire
shape is the canonical-key-order serialization of
supply_chain::trust_card::TrustCard (trust-card export requires
--json and pipes through to_canonical_json):
{
"schema_version": "trust-card-v1.0",
"trust_card_version": 14,
"previous_version_hash": "sha256:1234…",
"extension": { "extension_id": "npm:@example/plugin", "version": "2.4.1" },
"publisher": { "publisher_id": "pub-example", "display_name": "Example Org" },
"certification_level": "silver",
"capability_declarations": [],
"behavioral_profile": {
"network_access": true,
"filesystem_access": false,
"subprocess_access": false,
"profile_summary": "documented"
},
"revocation_status": { "status": "active" },
"provenance_summary": {
"attestation_level": "verified",
"source_uri": "https://registry.example/@example/plugin/2.4.1",
"artifact_hashes": ["sha256:abc…"],
"verified_at": "2026-05-15T09:11:02Z"
},
"reputation_score_basis_points": 7350,
"reputation_trend": "stable",
"active_quarantine": false,
"dependency_trust_summary": [],
"last_verified_timestamp": "2026-05-15T09:11:02Z",
"user_facing_risk_assessment": { "level": "medium", "summary": "…" },
"audit_history": [
{
"timestamp": "2026-05-15T09:11:02Z",
"event_code": "TRUST_CARD_UPDATED",
"detail": "scan completed",
"trace_id": "trust-scan-2026-05-15"
}
],
"derivation_evidence": null,
"camouflage_hints": [],
"card_hash": "sha256:5678…",
"registry_signature": "<HMAC-SHA-256>"
}
revocation_status is a serde-tagged enum: {"status": "active"} for
active cards, or {"status": "revoked", "reason": "...", "revoked_at": "..."} for revoked ones. certification_level is one of unknown,
bronze, silver, gold, platinum; reputation_trend is one of
improving, stable, declining; user_facing_risk_assessment.level
is one of low, medium, high, critical.
Lockstep verdict (verify lockstep) — illustrative shape; the
authoritative contract lives in docs/L1_LOCKSTEP_RUNNER.md and the
corresponding tests under tests/conformance/:
{
"schema_version": "v1",
"runtimes": ["bun", "franken-node"],
"excluded_runtimes": [
{
"name": "node",
"reason": "excluded because this host provides Bun's node wrapper rather than a real Node.js binary"
}
],
"status": "divergent",
"divergences": [
{
"fixture": "tests/cases/async-iter-001",
"node_hash": "sha256:…",
"bun_hash": "sha256:…",
"franken_node_hash": "sha256:…",
"first_diverging_step": 12
}
]
}
WorkflowTrace library verdict (replay::time_travel_engine). Its ReplayResult
serializes to this shape, with verdict rendered as "identical" or
"diverged" (the Diverged variant carries a usize count):
{
"schema_version": "ttr-v1.0",
"trace_id": "INC-2026-0042",
"verdict": "identical",
"steps_replayed": 4711,
"replay_duration_ns": 8521033,
"divergences": []
}
For a divergent replay, the verdict field serializes as
{"diverged": <n>} (the Diverged(usize) variant; external tagging
under #[serde(rename_all = "snake_case")]) and the divergences
array carries one or more entries, each with a typed DivergenceKind:
output_mismatch, side_effect_mismatch, full_mismatch, or
clock_drift with expected_ns, actual_ns, drift_ns,
tolerance_ns.
Counterfactual diff (incident counterfactual). The structured
report is built by incident_counterfactual_report_json in
crates/franken-node/src/main.rs and pins the schema to
"franken-node/incident-counterfactual-report/v1". Promotion fields
are null unless --promote was supplied with a signing key:
{
"schema_version": "franken-node/incident-counterfactual-report/v1",
"timestamp": "2026-05-16T14:22:31.118Z",
"bundle_id": "INC-2026-0042.fnbundle",
"incident_id": "INC-2026-0042",
"bundle_created_at": "2026-05-16T14:22:31.118Z",
"bundle_integrity_hash": "sha256:abc…",
"policy": "strict",
"evidence_refs": ["INC-2026-0042/timeline.jsonl"],
"total_decisions": 4711,
"changed_decisions": 17,
"severity_delta": -3,
"counterfactual_digest": "sha256:def…",
"counterfactual": { },
"promotion_contract": null,
"promotion_contract_digest": null,
"promotion_signature": null
}
JSON shapes are versioned; bumping a schema_version is a deliberate
change and must land alongside a golden update under tests/golden/.
Operator Glossary
| Term | Meaning |
|---|---|
| Trust card | Per-extension record with provenance, risk, audit history, camouflage assessment, revocation state. HMAC-signed. |
| Decision receipt | Signed record of a policy decision (quarantine, release, revoke, counterfactual promotion). Includes actor, action, subject, evidence refs. |
| Evidence ledger | Append-only signed log of decisions and observations, with witness traces. |
| Marker stream | Ordered stream of control-plane markers used to anchor MMR proofs and detect forks. |
| Audience token | Signed credential bound to a specific relying party (audience), with attenuation chain. |
| Audience binding | Property that a token's signature is only valid for the audience named in the canonical payload. |
| Validity window | Range of epochs (centered on the current epoch) within which an artifact is acceptable. Outside the window → fail closed. |
| Lane | Runtime execution lane (e.g., cancel, realtime) with its own concurrency cap, priority weight, and overflow policy. |
| Epoch | Monotonic control-plane counter advanced on every policy change, fleet action, or schema migration. |
| Capsule | A bounded, versioned, signed structure carrying an artifact and its provenance (for example, a replay capsule). |
| Bundle | A self-contained, deterministic snapshot exported for offline use (for example, an incident bundle). |
| Convergence | The state in which all nodes in a fleet zone have applied a control action. Tracked per-node. |
| Safe mode | Operator-authorized degraded mode that suspends capability issuance and refuses new decisions pending investigation. |
| Divergence receipt | Signed record of behavioral disagreement across runtimes during lockstep validation. |
| Camouflage assessment | Trajectory-based risk score detecting an extension that drifts from benign to malicious. |
| MMR proof | Inclusion or prefix proof in a Merkle Mountain Range; cheaper than a full Merkle tree on append-only logs. |
| VEF receipt | Verifiable Execution Fingerprint: a linked, signed receipt over an execution segment. |
| Fragility | DGIS classification of a dependency-graph node based on maintainer/publisher patterns and SPOF properties. |
| ATC reciprocity | Federation property: a participant's access tier should be proportional to its contribution. |
| Revocation frontier | The set of currently-revoked artifacts at a given timestamp; checked for freshness on risky actions. |
| Idempotency window | Time-bounded de-duplication window for remote operations. |
Doctor Walkthrough
franken-node doctor is the primary diagnostic command. It exercises
policy activation, evidence readiness, environment health, and policy
decision routing in one pass.
What it checks
Behind doctor is the WorkspacePressureDoctor (ops::doctor), which
classifies the workspace into a DoctorStatus:
| Status | Label | Meaning |
|---|---|---|
Healthy |
HEALTHY |
No pressure detected. |
Warning |
WARNING |
Minor issues; monitor but continue. |
Degraded |
DEGRADED |
Significant pressure; action recommended. |
Critical |
CRITICAL |
Immediate action required. |
The doctor consumes a WorkspacePressureInputs snapshot (free disk
bytes, target-dir bytes, active builds, memory pressure ratio, RCH
status, file-reservation count, agent-mail coordination health) and
produces a DoctorOutput carrying:
schema_version,timestamp: pinned schema version and report generation time.status: one of the fourDoctorStatuslevels above.summary: one-line human-readable state.resources: aResourceSummarywith the inspected values plus human-readable strings (free disk bytes/human, target-dir bytes/human, active builds, memory pressure ratio, RCH status, active reservations, coordination health).policy_decisions: aBTreeMap<String, PolicyDecisionSummary>keyed by work-class, each summary carryingwork_class,admission, andreason_codefor the routing decision.recommended_actions: aVec<RecommendedAction>keyed bypriority("high","medium","low") with a short description per action.diagnostics: operator-readable detail messages.metadata: machine-readable annotations.
Reading a doctor report
franken-node doctor --verbose --json | jq '.status, .resources.free_disk_human, .recommendations[].description'
For workspace-pressure specifically:
franken-node doctor workspace-pressure
franken-node doctor workspace-pressure --human-output pressure.txt
franken-node doctor workspace-pressure --json --conservative > pressure.json
--conservative keeps the threshold tight (recommend earlier);
--permissive only relaxes thresholds in known-clean environments.
Other doctor subcommands
franken-node doctor close-condition --json [--receipt-signing-key …]emits a dual-oracle close-condition receipt.franken-node doctor evidence-readiness --input broker-snapshot.json --jsonreports evidence freshness from that--inputsnapshot (not a live broker).franken-node doctor --policy-activation-input <fixture>exercises the policy activation contract against a checked-in snapshot JSON (not a live guardrail probe), useful for catching drift between the policy code and the spec.franken-node doctor process-spawn-readiness --jsonsecurely resolves (or accepts--bubblewrap-pathfor) a root-owned, non-setuid, non-writable Bubblewrap binary and runs a bounded functional user/PID/cgroup/IPC/UTS namespace probe. It returns non-zero withunavailableorunsupported; readiness by itself does not grantprocess_spawnor replace the signed opt-in.
doctor is the single best command to run before opening a support
ticket: its output (or --json artifact) is everything an investigator
needs.
Validation Broker Architecture
Library modules (ops::validation_broker, ops::validation_planner,
ops::validation_proof_cache, ops::validation_proof_coalescer,
ops::validation_proof_debt_ledger) model the evidence pipeline between
RCH workers, the proof cache, and operator close-out flows. The default
CLI does not start a live broker daemon: ops validation-readiness,
ops validation-closeout, and proofs queue status inspect --input /
--receipt snapshot files (live_broker=false).
Components
| Component | Purpose |
|---|---|
| Validation broker | Front door for evidence: accepts proof submissions, deduplicates, and emits receipts. |
| Validation planner | Plans the proof-class lanes a piece of work needs to traverse (e.g. cargo check, source-only audit, full conformance). |
| Proof cache | Memoizes proof results by canonical input hash; subject to GC and freshness gates. |
| Proof coalescer | Merges concurrent in-flight proofs targeting the same hash so the cluster never duplicates expensive work. |
| Proof debt ledger | Tracks proof obligations that have not yet been satisfied, with priority and deadline metadata. |
| Evidence index | Searchable surface over the evidence ledger for ops queries. |
Flow
operator ──► validation-planner ──► proof lanes (RCH workers + local)
│ │
v v
debt ledger proof coalescer
│
v
proof cache
│
v
validation broker (receipt)
│
v
evidence ledger
Operator surface
| Command | What it does |
|---|---|
franken-node ops validation-readiness --input broker-snapshot.json --json |
Reports per-lane freshness vs. policy from an --input snapshot (live_broker=false). --json emits the schema-versioned readiness report. |
franken-node ops validation-closeout --bead-id <id> --receipt <r> --json |
Renders the closeout summary from a --receipt snapshot (not a live broker). --json emits the schema-versioned closeout report. |
franken-node ops resource-governor --process-snapshot <p> --requested-proof-class <c> --json |
Advises whether the proof lane should run, defer, or dedupe. --json emits the schema-versioned governor decision. |
franken-node verify recovery-runbook --readiness-input <input> --json |
Generates a runbook from that readiness snapshot (not a live broker). --json emits the schema-versioned runbook report. |
Snapshot receipts carry the canonical input hash, the proof-class lane that produced them, the worker identity, the policy profile in force, and an issuance timestamp. Inspecting those files is not proof that a live broker is running.
Building a franken-node Extension
A franken-node-compatible extension is a Node/Bun-style package plus a signed publication manifest. The publish path is fail-closed: an unsigned, unversioned package cannot be admitted to the registry under the default profile.
Minimum requirements:
- Package layout. Build your Node package, then archive its
package.jsonand entrypoint into a single artifact file such asdist/plugin.tgz.registry publishreads file bytes; it does not package a directory for you. - Ed25519 signing key. A 32-byte raw private key. franken-node
accepts the key file in hex, base64, or a supported JSON wrapper.
Generate a random 32-byte seed in an accepted encoding; store it under
.franken-node/keys/(the bootstrap layout.gitignores this directory). - Explicit version.
--version <semver>onregistry publish; there is no implicit "latest". - Source metadata. Run from your Git checkout with a committed source
revision and an
originURL. The local publisher collects that metadata and signs its publication manifest and provenance with the supplied key. This is local operator publication, not independent build attestation or external publisher authorization.
End-to-end publish + verify:
# From the extension's Git checkout, with package.json and index.js built:
mkdir -p dist
tar -czf dist/plugin.tgz package.json index.js
# One-time: create a publisher seed without overwriting an existing key.
# Keep the key private; OpenSSL's Ed25519 PEM output is not accepted here.
umask 077
mkdir -p .franken-node/keys
(set -o noclobber; openssl rand -hex 32 > .franken-node/keys/publisher.ed25519)
# Publish a file into the local on-disk registry.
franken-node registry publish ./dist/plugin.tgz \
--version 1.2.3 \
--signing-key .franken-node/keys/publisher.ed25519 \
--json > publish.json
# Use the actual extension_id returned by publish (not an npm package name).
extension_id=$(python3 -c 'import json; print(json.load(open("publish.json"))["extension_id"])')
franken-node registry verify "$extension_id" --json
# Find the local publication with the CLI's level-2 assurance floor.
franken-node registry search plugin --min-assurance 2
# Periodic GC of older lineage entries
franken-node registry gc --keep 5
For long-lived publisher identities, rotate the signing key on a regular
cadence and update the corresponding trust card snapshot via
trust scan --audit so dependents see the rotation in their next
trust sync.
What the registry does on publish
- Reads the package bytes; rejects above
bounded_readpolicy limit. - Computes the canonical artifact hash.
- Validates the signature against the publisher's public key in constant time.
- Checks
minimum_assurance_levelagainst the supplied attestation metadata. - Writes the active artifact to
.franken-node/state/registry/artifacts/. - Updates the per-lineage active set and archives anything beyond
--max-active-artifacts.
franken-node registry gc moves stale artifacts to
.franken-node/state/registry/archive/ rather than deleting them, so an
operator can always recover a prior version.
Integration Patterns
franken-node is designed to drop into existing operations stacks. Common integrations:
CI/CD pipelines
# GitHub Actions: audit + default Bun+franken dyad (not L1 Node spec)
- name: franken-node migration audit
run: |
franken-node migrate audit . --format sarif --out audit.sarif
- name: franken-node lockstep dyad
run: |
# Default dyad is not the L1 spec. Matching only Bun stays fail.
# On runners with real Node.js, use --runtimes node,bun,franken-node
# (Node is then the spec).
franken-node verify lockstep . --runtimes bun,franken-node \
--emit-fixtures
- name: Upload SARIF for code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: audit.sarif
The exit code of migrate audit, verify lockstep, and verify release is 0 on pass and non-zero on fail; CI integration is
straightforward.
Container orchestration
In a Kubernetes-style deployment, run franken-node fleet agent --zone <zone> --poll-interval-secs 30 as a long-lived sidecar or daemonset.
The agent polls the local file-transport action log (not a live
control plane) and applies records with bounded retries. Liveness can be
tied to franken-node ops health-check --json (compiled git SHA plus
local ledger/receipt files, not per-surface daemon liveness). Readiness
can be tied to franken-node ops validation-readiness --input <snapshot> --json; omitting --input uses an empty default snapshot
(live_broker=false), not a live broker.
Monitoring stack
Scrape franken-node ops metrics --format prometheus from your
Prometheus deployment under the franken_node_* namespace. Useful
alerts against the currently-emitted surface:
franken_node_health_pass == 0: this CLIops health-checkfailed (missing compiled git SHA or no local ledger/receipt file), not per-surface daemon liveness.time() - franken_node_last_successful_evidence_ledger_flush_timestamp_seconds > 600: evidence ledger has not flushed in the last 10 minutes.rate(franken_node_evidence_ledger_spill_entries[5m]) > 0: the ledger is spilling at a non-zero rate (capacity pressure).sum(franken_node_fleet_active_quarantines) > 0: any quarantine is currently active across any zone.absent(franken_node_process_uptime_seconds): theops metricsscrape is down (this is a CLI scrape, not a node daemon heartbeat).
SIEM / audit pipelines
Ship the JSONL emitted by --structured-logs-jsonl to Splunk, Elastic,
or your SIEM of choice. The trace_id field is intended for cross-
process correlation. For long-term audit, copy
.franken-node/state/evidence-ledger/ (when materialized) and signed
decision receipts into immutable storage.
Package registries
franken-node trust scan inventories declared dependencies across the root
and captured workspaces, including development dependencies and reachable
transitive requirements. It uses npm-shrinkwrap.json when present, otherwise
package-lock.json, and follows the nearest captured installation at each
dependency location. Versions and integrity hashes come from that same capture;
an unrelated hoisted package cannot supply a nested package's evidence. An npm
alias is checked under its actual target identity (npm:package-name).
run uses the same traversal for production, optional, and peer requirements
from its selected project authority, excluding development-only branches.
Known revoked or quarantined transitive identities therefore reach the existing
profile-specific admission checks. These are bounded metadata checks: a lockfile
does not authenticate installed source bytes or enumerate undeclared dynamic
imports. Missing optional or peer targets remain visible as declared identities.
The native worker receives every identity considered by preflight over its authenticated control channel and rereads current signed trust state before loading the entrypoint. A card that becomes revoked, quarantined, or High/Critical risk during worker startup is evaluated under the selected profile again; required cards and registries cannot silently disappear. Previously untracked identities are rechecked too. A legacy-risky run whose registry was unavailable still checks any valid registry that appears before execution, and revocations block every profile. The native supervisor continues rechecking this admitted dependency set while the guest runs and interrupts it when the same profile rules deny current trust state. This does not intercept each module import or attribute undeclared dynamic code to a package identity.
Trust cards are keyed by package identity. When reachable installations contain
multiple versions, or mix a pinned version with an unresolved requirement, a new
card reports multiple-or-unresolved, has at least Medium risk, and carries a
warning. Single-version --deep/--audit refresh is unavailable for that card;
the scanner does not use one selected version to clear all the others.
--deep fetches package metadata from the public npm registry
(registry.npmjs.org) and dependent counts from api.deps.dev; those
two hosts, over HTTPS, are the only ones the scan will contact, so
private registries and npm registry mirrors are not supported.
--audit and trust sync query OSV (api.osv.dev, or
FRANKEN_NODE_OSV_QUERY_URL; during trust sync a clean answer from a
non-default endpoint is refused as a reason to lower a card's risk). The
[registry] section in franken_node.toml governs the local extension
registry's signature and provenance requirements, not npm.
The Engine-Split Contract
franken-node is a product layer: The JavaScript/TypeScript runtime
internals live in the sibling repository
franken_engine.
The boundary is enforced by the
Engine Split Contract and a duplicate-
implementation CI gate.
| franken_engine owns | franken_node owns |
|---|---|
| Native runtime internals (VM, parser, AST, interpreter, GC) | Compatibility capture from Node/Bun ecosystems |
| Policy semantics and trust primitives at the VM level | Migration and operator experience (audit, rewrite, validate, rollout) |
| Typed runtime-evidence authority and verification-identity contracts | Persistent product-root custody, per-session authority provisioning, signed identity capture, and worker-response reconciliation |
| Bayesian sentinel inference and containment actions | Extension ecosystem and trust distribution (registry, trust cards, reputation) |
| Native Rust execution (no V8/QuickJS bindings) | Packaging, rollout, and file-transport fleet log |
| Product-layer policy controls and verification surfaces | |
L1 Product Oracle (Node is the compatibility spec; matching only Bun stays fail; child_process native-eval aborts remain fail) |
|
| Fleet file-transport quarantine log (not a live multi-node plane) |
The split keeps the verifier SDK auditable: a third party can verify
product-layer claims (trust, migration, replay, fleet) without having to
audit the engine internals, and vice versa. The engine is a Cargo.toml
path dependency on a sibling checkout (../franken_engine). A path
dependency pins nothing: a build uses whatever that checkout contains, so
reproducing a binary needs the engine commit recorded alongside it.
For native runs, the product parent retains a persistent evidence root outside the execution child, provisions a fresh short-lived engine signing authority per session, and persists a root-signed public identity capture. The child receives only the session authority; the parent rejects any returned evidence identity that does not match its signed capture. Verifiers must obtain the product root independently—the root embedded in a capture is not a trust anchor. See the runtime-evidence authority handoff.
Security Posture
The frankenengine-node crate enforces these properties at the source level
and across the test surface:
#![forbid(unsafe_code)]in bothlib.rsandmain.rs; nounsafeblocks are permitted.- Constant-time comparison for every signature, hash, MAC, content-hash,
trace-id, and action-id check via
security::constant_time::{ct_eq, ct_eq_bytes}(backed by thesubtlecrate). - Saturating arithmetic on every counter, sequence, epoch, and timestamp computation that could meaningfully overflow.
- Bounded
Vecgrowth viapush_bounded(defined inlib.rs); rawVec::pushis gated out of capacity-critical contexts. Zero-capacity paths clear rather than panic. - Domain-separated, length-prefixed hashing with
b"<module>_<function>_vN:"domain prefixes and explicit length-prefix framing on each variable-length input. - Atomic rename + directory fsync for all durable writes (decision receipts, replay bundles, fleet transport state, control-epoch state).
- Bounded file reads through
bounded_read{,_to_string}(defaults reject parser-bomb inputs above policy limits). - Bounded source-IP cardinality in
AuthFailureLimiterso brute-force and connection-exhaustion vectors cannot grow unbounded source tracking. - Fail-closed expiry (
now >= expires_at), fail-closed signature verification, fail-closed trust-anchor requirements onincident replayandverify release. - Schema version pinning in
schema_versions.rswith upgrade and downgrade helpers for every serialized artifact. - CI gates at
.github/workflows/enforce closer discipline, golden artifact integrity, no-contract-no-merge, claim gates (ATC, BPET, DGIS, VEF), and execution-normalization; ~436scripts/check_*.pyvalidators back individual contracts.
Testing and Verification
- ~4,100
#[test]cases in the workspace test trees (tests/integration,tests/conformance,tests/contract,tests/e2e,tests/golden,tests/security,tests/perf) are registered on the defaultcargo test -p frankenengine-nodetarget, but the suites are feature-gated: the flagship CLI e2e families (fleet_cli_e2e,trust_cli_e2e,migrate_cli_e2e) require--features test-supportand mostbd_*conformance families require--features advanced-features, so a truly default-feature run executes only the ungated subset. CI and the verification lanes run with those features enabled. Another ~22,000 inline#[cfg(test)]unit tests are compiled out of a defaultcargo testby the crate-level#![cfg(any(not(test), franken_node_inline_tests))]gate and only run on the dedicated inline lane (see below). The inline library-test portion is guarded by.github/workflows/inline-lib-tests-gate.yml. Pull requests run a cheap preflight that asserts the dedicated inline-test override remains wired; the expensive full lane is invoked manually withrun_full_inline_tests=true, which runs the extended-surfaces/test-supportcargo test --libharness under--cfg franken_node_inline_tests. - Loom-based concurrency interleaving tests for the auth failure limiter, operator process-start initialization, evidence-ledger append ordering, and the remote-cap replay token set.
- Property and metamorphic suites for storage, control-lane policy, control-epoch, trust card, replay window, replay bundle event reorder, time-travel, artifact signing, decision-receipt round trip, activation pipeline, epoch-key derivation, threshold-signature quorum, evidence-ledger chain order, canonical serializer, and observability witness.
- Adversarial trust-card forgery, supply-chain poisoning, and BPET
adversarial evolution suites under
tests/security/. - Mock-free end-to-end pipelines for transparency inclusion, migration, revocation registry, incident bundle lifecycle, trust-card lifecycle, control-epoch lifecycle, quarantine-registry lifecycle, evidence-ledger lifecycle, fork-detection lifecycle, marker-stream lifecycle, audience token chain, MMR proofs, evidence-replay gate, divergence gate, key-role separation, control-lane scheduler, cancellation protocol, epoch-transition barrier, and provenance gate.
- Criterion benchmarks under
[[bench]]for Cuckoo revocation, BLAKE3, replay-bundle gzip, trust-card canonical encoding, proof-verifier gate, anti-entropy insert, threshold-signature verify, and a consolidatedperf_winssuite.
Run focused suites:
cargo test -p frankenengine-node # ~4.1k integration tests registered; ungated subset runs on default features (CLI e2e need --features test-support; inline unit tests need the inline lane)
cargo test -p frankenengine-node fleet_cli_e2e # CLI/integration
cargo test -p frankenengine-node verify_release_cli_e2e
cargo test -p frankenengine-node doctor_policy_activation_e2e
For the verifier SDK in isolation:
cargo test -p frankenengine-verifier-sdk
For the complete multi-file compatibility project collection:
python3 -m unittest discover -s tests -p test_project_compat_corpus.py
python3 scripts/project_compat_runner.py --corpus-dir fixtures/compatibility/projects \
--bin ./target/release/franken-node --node /path/to/node --bun /path/to/bun \
--artifacts-dir /tmp/franken-node-project-results
The artifact directory must be new. Omitting both --corpus-dir and --corpus
selects only projects/corpus.json; it does not run the other project manifests.
The full collection currently contains 170 cases in 26 manifests. The first
command checks real Node against every frozen expectation. The runner executes
distinct Node, Bun and native binaries with separate workspaces, retaining their
hashes, exact streams, statuses and expected file observations. Reference-version
differences remain non-pass outcomes. In particular, Node 24/26 differ from the
pinned Node 22.14.0 on some Blob, dotenv and Web stream cases; no one version is
currently qualified for the whole collection. These project results are separate
from the historical 560-case compatibility score. A test double or a reference-only
run supplies no native compatibility proof.
Honesty Manifest
The headline claims in this README obey the same rule as decision receipts and benchmark reports: claims require evidence, and the evidence is re-verifiable outside the producing runtime. Two committed artifacts back them:
docs/honesty_manifest_evidence.json— the census: a per-source snapshot of the committed tree from which each claim is derived (one{source, count}entry per.rsfile that contributes#[test]attributes, one per registered fuzz target, one per validator script, etc.). This is the "committed tree snapshot" any third party can regenerate from source.docs/honesty_manifest.json— the signed manifest: for each claim it binds the recomputed value, the README's pinned value, a drift tolerance, and asha256evidence_digestover the matching census entry, plus a singlecorpus_digestover all claims and a detached Ed25519 signature over the canonical unsigned payload.
The verifier SDK recomputes the whole chain with zero trust in the producer:
use frankenengine_verifier_sdk::honesty_manifest::{
verify_honesty_manifest, HonestyTrustAnchor,
};
let manifest = std::fs::read("docs/honesty_manifest.json")?;
let evidence = std::fs::read("docs/honesty_manifest_evidence.json")?;
let verified = verify_honesty_manifest(
&manifest, &evidence, &HonestyTrustAnchor::HarnessDefault,
)?;
println!("verified {} headline claims", verified.claim_count);
verify_honesty_manifest (1) verifies the Ed25519 signature against the pinned
reproducible harness key — or an operator-supplied trust anchor for a
re-signed production manifest; (2) recomputes every claim value from the
census and checks each evidence_digest; (3) enforces the README-claim
tolerance band; and (4) checks the corpus_digest commits to exactly the claim
set presented. Flipping a single census count, recorded value, or signature
byte makes it fail closed.
Two CI layers keep the manifest honest. scripts/check_claims_manifest.py --check-honesty continuously checks the committed census against the live
tree (no crypto required), so headline numbers cannot silently drift; the
verifier-SDK conformance test
(sdk/verifier/tests/honesty_manifest_recompute.rs) performs the load-bearing
Ed25519 + recompute verification on every cargo test run. Regenerate after a
deliberate change with:
python scripts/check_claims_manifest.py --update-honesty
Conformance Harnesses
Conformance harnesses are stronger than tests: they encode protocol
contracts as runnable code that produces signed evidence. The major
categories shipped in tests/conformance/:
| Harness | Asserts |
|---|---|
cancellation_protocol_conformance |
Cooperative cancellation completes within bounded time and produces a typed termination receipt |
connector_lifecycle_transitions |
The connector state machine respects the published transition diagram for every pair of states |
connector_protocol_harness |
Connectors implement the protocol contract on the wire (feature advanced-features) |
verifier_sdk_capsule_replay |
The verifier SDK's replay path produces the same verdict as the runtime for every fixture capsule |
verifier_session_monotonic |
Verifier sessions are monotonic: a sealed session cannot be reopened or rewritten |
idempotency_key_derivation |
Idempotency keys are deterministic functions of canonical inputs (feature extended-surfaces) |
control_policy_evidence_required |
Every control-plane decision is accompanied by evidence references the verifier can recompute |
replay_bundle_integrity_conformance |
Replay bundles round-trip through serialize/sign/verify without semantic loss |
replay_trace_canonical_conformance |
Replay traces serialize to the canonical form independent of insertion order |
replay_coverage_gate_conformance |
Replay coverage artifacts conform to the documented gate schema |
incident_bundle_integrity_conformance |
.fnbundle artifacts pass the same integrity gauntlet on read as on write |
frankensqlite_adapter_conformance |
The frankensqlite storage adapter honors the documented isolation and durability semantics |
fleet_decision_contract_harness |
Fleet decision receipts conform to the contract shape, including evidence refs |
canonical_serializer_conformance |
All serialized artifacts produce the documented canonical bytes |
Conformance results are emitted as signed artifacts, so an external verifier can recompute the same verdict on the same input.
Adversarial Test Suites
tests/security/ is a focused adversarial-corpus surface. Each suite is
deliberately constructed to violate an invariant; the runtime must
refuse cleanly.
| Suite | Adversary's goal |
|---|---|
adversarial_trust_card_forgery |
Forge a trust card snapshot that bypasses HMAC verification |
adversarial_supply_chain_poisoning |
Smuggle a malicious dependency past the registry gates |
bpet_adversarial_evolution_suite |
Drift a benign extension into malicious behavior without tripping camouflage detection |
dgis_quarantine_containment |
Find a contagion path that escapes DGIS quarantine |
dgis_fragility_spof |
Cause an SPOF cascade that the fragility model fails to catch |
dgis_graph_ingestion |
Inject a malformed dependency graph that confuses the topology audit |
dgis_contagion_simulator |
Find a contagion trajectory the simulator misses |
dgis_adversarial_suite |
Bundle of adversarial graph-topology attacks |
threshold_signature_verification |
Defeat k-of-n verification by replay, malleability, or partial signature |
obligation_tracked_channels |
Leak obligations across cancellation boundaries |
anti_amplification_harness |
Use the protocol as an amplification vector |
camouflage_detector |
Defeat camouflage detection with a structured trajectory |
exfiltration_sentinel_scenarios |
Exfiltrate state past the runtime's sentinels |
signed_lineage_graph_builder |
Construct a lineage graph that passes the builder but fails downstream verification |
dgis_immunization_planner |
Defeat immunization planning by exploiting brittleness of the recommendation |
Each suite is paired with a fixture corpus under fixtures/ and runs as
part of the normal cargo test invocation. New suites land alongside
new threat models so the test surface keeps pace with the threat model.
Operational Runbooks
The runbook set under docs/runbooks/ covers the
incident classes that recur in operation. Each is a short, action-
ordered playbook.
| Runbook | When to consult |
|---|---|
epoch_transition_failure.md |
Epoch advancement aborted or stalled at the barrier |
evidence_ledger_divergence.md |
Two replicas disagree on the evidence ledger contents |
fleet_quarantine_activation.md |
A fleet-wide quarantine has been issued and must converge |
mass_revocation_event.md |
Bulk revocation hit a sensitive number of extensions at once |
obligation_leak_incident.md |
Obligations were not released within the SLA window |
cancel_timeout_incident.md |
A cooperative cancel did not complete within the timeout |
proof_pipeline_outage.md |
The VEF proof pipeline became unavailable |
proof_lane_readiness_blockers.md |
Specific proof lanes are blocked from readiness |
region_quiescence_breach.md |
A region failed to quiesce within the quiescence window |
telemetry_bridge_operator_guide.md |
Detailed reference for the telemetry-bridge integration |
trust_state_corruption.md |
The local trust-card registry has been corrupted |
The runbook index lives at docs/runbooks/README.md.
Migration Pipeline Stages
franken-node migrate is a four-stage pipeline with explicit outputs
between stages. Each output is a deterministic artifact: rerunning the
same stage on the same input produces the same bytes.
flowchart LR
src["source code<br/>Node / Bun project"] --> audit["AUDIT<br/><code>migrate audit</code>"]
audit --> auditOut["migration-audit.json<br/>(SARIF / JSON / text)"]
auditOut --> rewrite["REWRITE<br/><code>migrate rewrite --apply</code>"]
rewrite --> rewriteOut["transformed source<br/>+ rollback-plan.json"]
rewriteOut --> validate["VALIDATE<br/><code>migrate validate</code>"]
validate --> validateOut["validation receipt<br/>(conformance + lockstep)"]
validateOut --> rollout["ROLLOUT<br/><code>fleet release</code>"]
rollout --> rolloutOut["fleet release receipts<br/>(signed, per-zone convergence)"]
classDef stage fill:#1f6feb,color:#fff,stroke:#0a3b91;
classDef artifact fill:#f0c674,color:#0b1020,stroke:#a8884c;
class src,audit,rewrite,validate,rollout stage;
class auditOut,rewriteOut,validateOut,rolloutOut artifact;
Stage details:
- Audit (
migrate audit <path> --format json|text|sarif --out <p>) walks the source tree, classifies each finding, and emits a deterministic inventory. SARIF output is the recommended format for CI integration because it's natively understood by GitHub Code Scanning and most IDEs. - Rewrite (
migrate rewrite <path> --apply --emit-rollback <p>) applies the planned transforms. Without--applythe command is a dry-run.--emit-rollbackwrites an unsigned JSONMigrationRollbackPlan(not Ed25519-signed) plus.migrate-backup/snapshots so the transformation can be unwound from file contents. - Validate (
migrate validate <path>) reruns static migration audit checks and, unless--static-only, a transformed-runtime smoke test (franken-node, node, or bun). It does not runverify lockstep. Doctor WARNs whenmigration.require_lockstep_validationis false; that flag does not makemigrate validateinvoke lockstep. - Rollout uses the configured fleet store (the local file
transport, or a
fleet servecoordinator):franken-node fleet release --incident <migration-id>emits the signed release receipt and waits for fleet convergence.
franken-node migrate-report produces a single-document operator
assessment that summarizes the audit and (if run) the validation result
in HTML or JSON.
Operator Quick Reference
A one-table cheatsheet for common operator flows. Each entry expands into the corresponding section above.
| Goal | Command |
|---|---|
| Adopt | franken-node init --profile balanced --scan |
| Audit a project | franken-node migrate audit . --format sarif --out audit.sarif |
| Compare Bun+franken dyad (not L1 Node spec) | franken-node verify lockstep . --runtimes bun,franken-node |
| Compare with real Node as spec | franken-node verify lockstep . --runtimes node,bun,franken-node (only when node is real Node.js; matching only Bun stays fail) |
| Seed trust cards | franken-node trust scan . --deep --audit |
| Refresh trust | franken-node trust sync --force |
| Run in strict mode | franken-node run ./app --policy strict |
| Inspect a card | franken-node trust card npm:@example/plugin --json |
| List high-risk | franken-node trust list --risk high --json |
| Quarantine an artifact | franken-node trust quarantine --artifact sha256:… |
| Lift quarantine | franken-node fleet release --incident INC-… --json |
| Reconcile fleet | franken-node fleet reconcile --json |
| Snapshot incident | franken-node incident bundle --id INC-… --verify |
| Verify recorded incident | franken-node incident replay --bundle …fnbundle --trusted-public-key …pub |
| Re-execute captured native guest | franken-node incident replay --bundle …fnbundle --trusted-public-key …pub --execute |
| Run a counterfactual | franken-node incident counterfactual --bundle … --trusted-public-key … --policy strict |
| Diagnose environment | franken-node doctor --verbose --json |
| Workspace pressure | franken-node doctor workspace-pressure (human stdout; --human-output <path> writes a file) |
| Publish an extension | franken-node registry publish ./dist/plugin.tgz --version 1.2.3 --signing-key … |
| Verify a release | franken-node verify release ./release-dir --key-dir ./trusted-keys |
| Issue capability | franken-node remotecap issue --scope … --endpoint … --ttl 300 |
| Audit config | franken-node ops config-audit --json |
| Export metrics | franken-node ops metrics --format prometheus |
| Enter safe mode | franken-node safe-mode enter --reason … --operator-id … --trust-state-hash … |
Fuzzing and Differential Testing
fuzz/fuzz_targets/ ships 146 registered cargo-fuzz harnesses targeting the
adversarially-exposed surfaces: parsers, deserializers, signature
verifiers, lifecycle inputs, canonical encoders, transcript readers. A subset
is undergoing API-drift remediation; the verification-target compile census
(.github/workflows/verification-target-compile-gate.yml) builds them with
--keep-going and tracks the compiling ratio. Representative harnesses:
fuzz_canonical_serializer_roundtrip: every canonical encoder must satisfydecode(encode(x)) == xfor arbitrary inputs.fuzz_canonical_serializer_deserialize: every deserializer must refuse malformed bytes without panicking.fuzz_constant_time_comparison:ct_eqis exhaustively probed for observable timing or branch leakage on adversarial inputs.fuzz_capability_token_parser: adversarial inputs to the capability token parser cannot crash, allocate without bound, or accept invalid tokens.fuzz_connector_lifecycle_inputs: the connector state machine refuses every malformed transition input.fuzz_compromise_reduction_report_json: adversarial JSON cannot exploit the compromise-reduction report parser.fuzz_api_request_deserialization: control-plane API request parsing is closed under adversarial bytes.fuzz_config_toml_parse: adversarial TOML cannot crash or trigger unbounded allocation in the config loader.
fuzz/regression/ is meant to keep discovered failure inputs as permanent
regression cases, but today it holds 5 inputs for 2 of the 146 targets
(shim, migration, dated 2026-02). The legacy category adapter reads these
seeds through simulated functions under fuzz/targets/; that does not establish
regression replay across the 146 registered cargo-fuzz targets
(bd-fuzz-regression-corpus-f28ac). Coverage data lives in
fuzz/coverage/; fuzz artifacts live in fuzz/artifacts/. The
mutants-gate.yml workflow is configured to run cargo-mutants against the test
surface as a separate effective-coverage signal: a mutant that
survives indicates a gap in the test surface, not a bug per se. The gate
requires a registered mutation-adequacy floor (MUTATION_SCORE_FLOOR_BP)
on pull requests that touch the focused modules. It also requires nonzero
inline-test discovery for every focused module and readable, nonempty mutant
outcomes. Hosted execution and an actual mutation score remain unverified;
workflow configuration and gate-control tests do not supply that measurement.
Effective coverage, not a raw count
A raw test count is Goodhart-bait, so the honest "tests" signal is effective coverage, expressed as three recomputable parts rather than a single token count:
- Registered test surfaces for
cargo test -p frankenengine-node(thousands registered; the flagship CLI e2e suites require--features test-support, most conformance families--features advanced-features, and the ~22k inline unit tests run only on the dedicated inline lane). The Honesty Manifest recomputes source counts from the committed tree (Honesty Manifest); those counts do not establish discovery, execution, or success. Retain actual test-run results separately. - Mutation adequacy — the fraction of seeded mutants the suite catches,
to be measured by
mutants-gate.ymlagainst the registered floor (hosted execution remains unverified; see above). Mutation score measures whether the tests would catch a regression, which a count cannot. - A pass-rate confidence interval — the verifier SDK exposes the Wilson
score interval (
honesty_manifest::wilson_score_interval_bp) so an auditor can bound the executed-test pass rate from a measured(passed, trials)pair (e.g. 95/100 → a 95% lower bound of ~88.8%), rather than trusting a bare "all green."
The configured gate computes its score from run outcomes rather than a score baked into the committed manifest. A measured score requires a completed run; the source count and interval calculation remain separately recomputable.
Profile Differences in Detail
Profile selection changes concrete behavior. The salient differences:
| Behavior | strict |
balanced |
legacy-risky |
|---|---|---|---|
Stale or missing revocation frontier at run (trusted deps) |
refuse (5 min) | warn (1 h) | no floor |
High/Critical-risk dependency at run |
refuse (high_risk) |
warn | ignored |
| Auto-quarantine of dependencies | after an SSRF violation or sentinel escalation | same | warn-only |
| Lockstep evidence to leave rollout Shadow | required | required | required (all profiles; --force bypasses and is recorded) |
| Divergence receipts | always emitted | emitted in production | optional |
registry.require_signatures |
required | required | may be relaxed |
registry.require_provenance |
required | required | may be relaxed |
| Compatibility mode | tightest | balanced | permissive |
Guest file writes (fs.writeFileSync and friends; confined to the project root) |
denied | denied | allowed |
Allowlisted process metadata reads in native entrypoint code (process.argv, process.platform) |
denied unless runtime.allow_process_shape=true |
denied unless runtime.allow_process_shape=true |
allowed unless runtime.allow_process_shape=false |
process.env reads |
denied | denied | denied (no profile grants them yet) |
Pure builtins (JSON, Error, Number, path string operations) |
allowed | allowed | allowed |
| Compatibility corpus pass rate (560 cases, 2026-09-26, same binary, back to back) | 70.71%: the same as balanced except http 3/50, since strict grants no network egress |
78.93% (fs 2/50) | 86.61% |
| SSRF default | block | block | block |
| Safe-mode auto-entry on crash-loop | not implemented (the crash-loop detector has no caller) | same | same |
| Camouflage severity threshold for risk bump | constant 0.50, not yet per profile | same | same |
The profile string is canonicalized at load time and recorded in every decision receipt; replays under a different profile produce a counterfactual diff, not a bug.
For entrypoint scripts that need their arguments while retaining balanced
policy, set [runtime] allow_process_shape = true or run:
FRANKEN_NODE_RUNTIME_ALLOW_PROCESS_SHAPE=true franken-node run app.js -- alpha beta
The grant permits the engine's statically allowlisted process metadata,
including the actual supplied argument strings. Raw or aliased process,
computed property access, and environment values remain refused. It does not
add filesystem, network, or spawn capabilities. The current engine provides
argv, platform, and a synthetic pid; other allowlisted field names may
still be unpopulated. Required/imported modules use their independent engine
lowering policy and do not inherit this entrypoint grant.
Native dispatch reports expose invocation_settings: the parser budgets,
execution ceilings for both lanes, and process_shape_read_allowed grant
actually supplied to the engine. These settings survive typed invocation
failures, including parsing/lowering failures and uncaught guest exceptions,
and are bound into the persisted receipt and any signed run record. Failed
attempts leave the selected lane and completed containment decision absent.
The snapshot proves which settings were supplied, not that guest instructions
ran. Startup failures, interrupted workers, panics, and telemetry failures may
still have no reported settings; historical absence also remains unknown.
Native replay capture supports this bounded process-shape grant in strict and
balanced profiles. It retains the supplied process.argv strings and restores
them during offline execution, together with the original grant. The engine's
fixed platform and synthetic pid do not depend on the replay host. Captured
arguments may contain secrets, just like captured source and I/O. This does not
grant environment reads, child processes or module loading. Those require
their separate explicit capability and capture paths described in
native replay. Statically lowered
builtin facades can still use their recorded I/O transcript.
The Close-Condition Oracle
franken-node doctor close-condition emits a dual-oracle close-condition
receipt: the gate that determines whether the platform is complete
enough to ship. The contract is documented in
docs/DUAL_ORACLE_CLOSE_CONDITION.md.
Three oracle dimensions must all be GREEN:
| Dimension | Owner | Validates | Artifact |
|---|---|---|---|
| L1 Product Oracle | Track 10.2 | Targeted compatibility corpus vs Node (spec). Bun is a second reference; matching only Bun stays fail. child_process native-eval aborts remain fail (not recategorized as pass). |
artifacts/oracle/l1_product_verdict.json |
| L2 Engine-Boundary Oracle | Track 10.17 | franken_engine integration points conform to the engine-split contract | artifacts/oracle/l2_engine_verdict.json |
| Release Policy Linkage | Track 10.2 | Release gates consume both verdicts and enforce pass-through | artifacts/oracle/release_policy_verdict.json |
The gate is conjunctive:
PASS if and only if
L1.verdict == "GREEN"
AND L2.verdict == "GREEN"
AND release_policy.verdict == "GREEN"
No partial success is accepted; an AMBER or RED verdict on any
dimension blocks the close condition for shipping. CI section 10.N
runs check_oracle_close_condition.py --consistency-mode so an honest L1
RED (corpus below 95%) can merge while the ship gate stays fail-closed.
Declaring L1 GREEN below the corpus floor is still FAIL in both modes.
See docs/DUAL_ORACLE_CLOSE_CONDITION.md
(Consistency mode vs release/ship mode).
Receipts emitted by the close-condition oracle are signed and chained into
the evidence ledger so a later audit can verify exactly which inputs
produced the verdict. Live L1 is currently RED (corpus 69.82%, 391/560, per artifacts/13/compatibility_corpus_results.json);
do not read a GREEN L1 from backfill artifacts.
Performance Characteristics
Benchmarks live under crates/franken-node/benches/ and run through
Criterion. The current
[[bench]] set:
| Benchmark | What it measures |
|---|---|
cuckoo_revocation_bench |
Insert / lookup throughput against the Cuckoo-filter revocation index |
blake3_performance_bench |
BLAKE3 throughput on the franken-node hashing call sites (feature blake3) |
replay_bundle_gzip_bench |
Compression and integrity-check round-trip on replay bundles |
trust_card_canonical_bench |
Canonical encode/decode of a trust card snapshot |
proof_verifier_gate_bench |
VEF receipt verification under the gate path |
anti_entropy_insert_bench |
Anti-entropy log insert (feature advanced-features) |
threshold_sig_verify_bench |
k-of-n Ed25519 verify with cached public keys |
perf_wins |
Consolidated suite tracking historical wins to defend against regressions |
cargo bench -p frankenengine-node
cargo bench -p frankenengine-node --bench threshold_sig_verify_bench
Hot-path discipline is enforced by tests/hot_path_perf_budget_contract.rs
(feature policy-engine): performance budgets for the most-traveled
paths are encoded as conformance assertions, and a regression fails CI.
The bench run CLI surface produces a SHA256 provenance_hash report
(not Ed25519-signed). A verifier can recompute the hash over the report
body; that is tamper-evident content hashing, not an independently
signed attestation.
Reproduction Playbook
The claims in this README are reproducible. Substitute your own project paths and trust anchors where indicated.
# Default Bun+franken dyad (not the L1 Node spec; matching only Bun stays fail)
franken-node verify lockstep ./my-app \
--runtimes bun,franken-node \
--emit-fixtures
# When node is real Node.js, Node is the spec:
# franken-node verify lockstep ./my-app --runtimes node,bun,franken-node --emit-fixtures
# Reproduce a benchmark report
franken-node bench run --scenario secure-extension-heavy --output bench.json
# Recompute the SHA256 provenance_hash over the report body (tamper-evident
# content hash, not an Ed25519-signed SDK attestation).
# Reproduce an incident bundle and replay verdict
franken-node incident bundle --id INC-2026-0007 \
--evidence-path ./incidents/INC-2026-0007/evidence.v1.json \
--verify
franken-node incident replay --bundle ./INC-2026-0007.fnbundle \
--trusted-public-key ./keys/replay-trust-anchor.pub
# Reproduce a registry publish + verify cycle
franken-node registry publish ./dist/plugin.tgz \
--version 1.2.3 \
--signing-key .franken-node/keys/publisher.ed25519 --json > publish.json
extension_id=$(python3 -c 'import json; print(json.load(open("publish.json"))["extension_id"])')
franken-node registry verify "$extension_id" --json
# Reproduce the doctor activation telemetry (checked-in fixture)
franken-node doctor --verbose --json \
--policy-activation-input ./fixtures/policy_activation/doctor_policy_activation_pass.json
# Reproduce a counterfactual diff
franken-node incident counterfactual --bundle ./INC-2026-0007.fnbundle \
--trusted-public-key ./keys/replay-trust-anchor.pub \
--policy strict --json
The full reproduction playbook for headline claims is at
docs/reproduction_playbook.md, and
checked-in fixture inputs live under
fixtures/ at the repo root.
Reference Documentation Index
Beyond the README, the most-used reference documents under
docs/ are:
| Document | Purpose |
|---|---|
PRODUCT_CHARTER.md |
What franken_node is, what it isn't, governance, and decision rules |
ENGINE_SPLIT_CONTRACT.md |
Hard boundary between franken_node and franken_engine |
ARCHITECTURE_OVERVIEW.md |
Diagrammatic and prose architecture |
CAPABILITY_OWNERSHIP_REGISTRY.md |
Which crate owns which capability |
DUAL_ORACLE_CLOSE_CONDITION.md |
Three-oracle ship gate |
COMPATIBILITY_BANDS.md |
Behavior bands across compatibility modes |
L1_LOCKSTEP_RUNNER.md |
Reference for the lockstep oracle |
L2_ENGINE_BOUNDARY_ORACLE.md |
Reference for the engine-boundary oracle |
COMPATIBILITY_MODE_POLICY.md |
How modes map to runtime behavior |
reproduction_playbook.md |
How to reproduce headline claims |
COMMIT_STANDARDS.md |
Commit message standards for this repo |
runbooks/README.md |
Index of operational runbooks |
specs/ |
100+ contract specifications, one per gate/protocol |
adr/ |
Architecture decision records |
planning/ |
Relocated MODE_OUTPUT reports under docs/planning/modes/ |
progress/ |
Relocated BD implementation memos (BD_IWA3Z, BD_P9MPD5, BD_P9MPD7, BD_SH95A) |
Spec documents are part of the product contract: changes there must
land alongside the corresponding scripts/check_<gate>.py and
.github/workflows/<gate>.yml changes.
Common Pitfalls
Things operators wish they had known on day one:
- The engine-split contract is enforced. Building from source
without the sibling
franken_enginecheckout will fail at link time. Use the one-line installer unless you specifically need source builds. legacy-riskyis a migration aid, not a long-term mode. It relaxes revocation freshness, provenance, and signature gates so a legacy codebase can adopt incrementally. Leaving it in production defeats the trust-native premise.incident replaywill refuse without a trust anchor. There are no built-in trust roots.--trusted-public-key <path>or--key-dir <dir>is mandatory; if you've never set one, that is itself the answer to "why won't replay run?"registry publishrequires both--versionand--signing-key. There is no implicit version (it would defeat reproducibility), and there is no anonymous publish path.masteris for legacy URL compatibility only. Real work happens onmain. The two branches are kept synchronized; if you push tomain, alsogit push origin main:masterso install URLs stay fresh.br sync --flush-onlynever runs git. Beads stays non-invasive about VCS by design. Afterbr sync --flush-onlyyou mustgit add .beads/and commit manually.- Bare
bvlaunches an interactive TUI. In automated agent sessions or CI, use the robot flags (bv --robot-plan,bv --robot-priority,bv --robot-insights) instead. - Cargo work goes through
rch exec. Local builds storm a shared machine when many agents run simultaneously.rchfails open if workers are unavailable, so you can always fall back to localcargowhen alone. - The doctor's output is the diagnostic. Before opening a ticket,
run
franken-node doctor --verbose --jsonand attach the result. - Signed artifacts trump local caches. When trust state and a signed artifact disagree, the runtime trusts the artifact and refreshes the cache, not the other way around.
Verifier SDK
The crate frankenengine-verifier-sdk (at
sdk/verifier/) is a small, deliberately stable Rust
API for independent third-party verification. It re-implements the
verification side of the protocol so that a verifier does not need to
trust, or even depend on, the main frankenengine-node crate. It builds
and tests on stable Rust from sdk/verifier/ alone, with no workspace and
no franken-node crates: cargo test --manifest-path sdk/verifier/Cargo.toml.
The standalone workflow checks this (verifier-sdk-standalone.yml); a
configured workflow is not evidence that hosted Actions are enabled or green.
What it exposes:
incident_bundle:verify_incident_bundle(bytes, &trusted_signer)independently verifies a.fnbundleproduced byfranken-node incident bundle. It re-derives the integrity and decision-sequence hashes, rejects unsigned fields, and checks the Ed25519 signature under the verifier's own anchor.bundle: the SDK's ownReplayBundlebyte-stream format (serialize,deserialize,hash,integrity_hash,seal,sign_bundle,verify_ed25519_signature,verify_signed_bundle,verify). This is a separate format from the CLI's.fnbundle; useincident_bundlefor CLI output.bundle::verifychecks integrity only (integrity_hash, an unkeyed digest; schemavsdk-replay-bundle-v2.0carries no "signature" field). Authenticity requiresverify_signed_bundlewith a trusted key.capsule: verify a replay capsule, including schema-version checks and side-effect declaration matching.counterfactual: verify signed counterfactual receipts (it does not re-run incidents).honesty_manifest: independently re-verify the README's headline claims.verify_honesty_manifestrecomputes every claim from the committed per-source census, checks eachevidence_digestand thecorpus_digest, and verifies the manifest's Ed25519 signature against the pinned harness key (or an operator-supplied trust anchor) — failing closed on any tampered count, value, or signature byte. See Honesty Manifest.- A top-level
VerifierSdkplus thecreate_verifier_sdk(identity)helper.VerifierSdkexposesverify_claim,verify_migration_artifact,verify_trust_state,verify_signed_migration_artifact,verify_signed_trust_state,verify_workflow_execution,validate_bundle,append_transparency_log,execute_workflow,create_session,record_session_step, andseal_session. - Stable result types:
VerificationVerdict,VerificationOperation,ValidationWorkflow,TransparencyLogEntry,AssertionResult,VerificationResult,SessionStep,VerificationSession,VerifierSdkError.
The SDK is what a regulator, an auditor, or a research team uses to
validate franken-node's public claims. Its conformance is asserted by
tests/conformance/verifier_sdk_capsule_replay.rs and
tests/conformance/verifier_session_monotonic.rs.
Python and JS/TypeScript auditors can use the same independent Rust verifier
through its offline executable. Install it with Cargo from the SDK directory
in a standalone distribution (or use --path sdk/verifier in this checkout):
cargo install --path . --bin franken-verifier
franken-verifier verify --format incident-bundle \
--trusted-key-hex "$AUDITOR_PUBLIC_KEY_HEX" < incident.fnbundle
Distribute the executable together with
bindings/python/franken_verifier.py,
bindings/javascript/franken_verifier.mjs and its adjacent
franken_verifier.d.mts. The executable and public anchor must come from
sources the auditor trusts independently of the artifact. No producer
service, product/engine checkout, private signing key, Python package or JS
package installation is required. Python requires 3.10 or later; JavaScript
requires Node 18 or later. Running .mts directly additionally requires a
Node version with built-in TypeScript stripping; the consumer test uses Node
26. Both interfaces share the Rust cryptographic implementation; they are
separate language interfaces, not independent cryptographic implementations.
from pathlib import Path
from franken_verifier import verify_artifact, VerifierError
result = verify_artifact(
Path("incident.fnbundle").read_bytes(),
auditor_public_key_hex,
verifier="/trusted/bin/franken-verifier",
)
print(result["scope"], result["facts"]["incident_id"])
import { readFileSync } from "node:fs";
import { verifyArtifact, VerifierError } from "./franken_verifier.mjs";
const result = await verifyArtifact(
readFileSync("incident.fnbundle"), auditorPublicKeyHex,
{ verifier: "/trusted/bin/franken-verifier", timeoutMs: 10_000 },
);
console.log(result.scope, result.facts.incident_id);
Pass original bytes, without decoding/re-encoding the artifact in Python or
JavaScript. Results bind artifact_sha256 to those exact input bytes. The
versioned response is franken-verifier-cli/v1; errors throw VerifierError
with a stable code and optional process exit/status diagnostics. The CLI
exits 0 only for authenticated evidence, 1 for refusal and 2 for usage/I/O
errors. Inputs are limited to 64 MiB, JSON nesting uses the Rust decoder's
128-level limit, responses are bounded to 64 KiB, and callers can set a
timeout up to 120 seconds (default 10 seconds). The executable is trusted
code, not a sandbox for running arbitrary verifier programs.
| Explicit format | Authenticated scope | Signature input |
|---|---|---|
incident-bundle (default in bindings) |
authenticated_recorded_incident: signed recorded events, hashes and bounded native-capture metadata |
Embedded Ed25519 signature checked under the caller's anchor |
capsule |
authenticated_capsule_content: independently re-derived signed payload/input hash |
Signed SDK capsule JSON with strict fields |
signed-bundle |
authenticated_sdk_replay_bundle: the distinct canonical SDK bundle format |
Required detached signature_hex (Python), signatureHex (JS) or --signature-hex (CLI) |
These scopes do not execute a captured guest or establish original-policy equivalence. Integrity-only SDK bundles are never returned as authenticated without their detached signature. Unknown/duplicate fields, invalid UTF-8, unsupported formats, tampering and foreign anchors fail closed. A native capture result exposes only digest/size/terminal-state/exchange-count metadata, not captured source, arguments, exception text or host-I/O values.
sdk/verifier/tests/language_clients.rs runs real Python, JavaScript and
TypeScript consumers of a copied distribution. On Linux, bwrap hides the
product and engine checkouts; missing runtimes/isolation tools fail the test.
Independent Node-crypto vectors and planted broken-child controls check
signature, canonical-byte, protocol and failure propagation boundaries.
This function runs as the doc-test of verify_incident_bundle
(sdk/verifier/src/incident_bundle.rs), against a bundle the real CLI wrote:
use ed25519_dalek::VerifyingKey;
use frankenengine_verifier_sdk::incident_bundle::verify_incident_bundle;
/// Verify a `franken-node incident bundle` output against a signer key the
/// auditor obtained independently (never the key embedded in the bundle).
fn audit(
bundle_path: &std::path::Path,
trusted_signer: &VerifyingKey,
) -> Result<(), Box<dyn std::error::Error>> {
let bytes = std::fs::read(bundle_path)?;
let verified = verify_incident_bundle(&bytes, trusted_signer)?;
println!(
"verified incident {} ({} events, integrity {})",
verified.incident_id, verified.event_count, verified.integrity_hash
);
Ok(())
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
lockstep validation failed |
Behavior delta across runtimes | franken-node verify lockstep ./my-app --emit-fixtures and inspect generated divergence fixtures. |
revocation freshness gate denied … |
Under strict, a dependency is being admitted on the strength of its trust card, or a capability is being issued (remotecap issue, always 5 minutes), but the registry's recorded revocation frontier is more than 5 minutes old or was never recorded. Under balanced the same text appears as a preflight warning (1-hour window) and the run proceeds |
franken-node trust sync --force. Only a forced refresh in which every trust signal was fetched without network errors records a new frontier; cached answers do not. The frontier is stored in the durable trust registry, not inferred from file timestamps. |
artifact rejected: missing attestation |
Registry policy requires provenance proofs | Rebuild artifact with provenance metadata and re-sign before registry publish. |
quarantine not converged |
One or more nodes did not apply the control action in time | franken-node fleet status --verbose, then franken-node fleet reconcile. Re-check the convergence timeout in [fleet]. |
incident replay refused: no trust anchor |
--trusted-public-key / --key-dir missing on incident replay or incident counterfactual |
Supply an Ed25519 trust anchor; no built-in trust roots exist. |
incident replay nondeterministic |
Missing, corrupted, or stale evidence/bundle components | Re-export with franken-node incident bundle --id <ID> --verify or ensure <root>/.franken-node/state/incidents/<slug>/evidence.v1.json exists. |
registry publish refused: missing version / missing signing-key |
Fail-closed publish path | Pass --version <semver> and --signing-key <path> (raw Ed25519 32-byte key; hex, base64, or supported JSON wrapper). |
safe-mode entry refused: missing trust-state-hash |
Fail-closed safe-mode entry path | Supply --reason, --operator-id, and --trust-state-hash together. |
workspace pressure: defer from doctor workspace-pressure |
Disk / memory / build pressure crossed the active policy threshold | Run franken-node doctor workspace-pressure (human stdout) for the recommended action set; --human-output <path> writes that report to a file. Consider --conservative to keep validation gating tight, or --permissive only for known-clean machines. |
franken-node doctor --verbose --json is the single best diagnostic; it
exercises policy activation, evidence readiness, and environment health in
one pass.
Limitations
Legacy profile permits insecure behaviors.
legacy-riskyexists for constrained migration windows. It is not a long-term mode, and it does not ambiently permit child-process spawning.Child-process spawning is Linux/Bubblewrap-only and impossible by default. A signed, expiring, resolved-policy-bound
ChildProcessSpawnopt-in and a successful functional backend probe are both mandatory. Unsupported operating systems and missing or insecure Bubblewrap binaries fail closed. Current runtime-of-record work still withholds the capability until the authenticated native worker can prove it was launched inside that namespace.Strict profile blocks weak-provenance extensions. This is intentional but can surprise teams whose registries are not yet provenance-clean.
Migration rewrites target high-value patterns first. Niche framework macros may still need manual edits; the audit will tell you.
Fleet CLI is file-transport by default (
live_control_plane=false). A partitioned node will not converge untilfleet reconcile.Single-node mode stores fleet state locally; multi-node coordination runs through one
fleet servecoordinator. Its listener is plaintext HTTP (loopback by default): expose it beyond the host only behind TLS termination or an encrypted overlay.Counterfactual simulations depend on the completeness of the telemetry captured during the incident window. Sparse evidence produces sparse counterfactuals. The default counterfactual executor is a policy-threshold model that re-evaluates the recorded decision trace; the report labels which executor produced the diff so a synthetic re-evaluation is never mistaken for a live re-run.
franken-node runexecutes through the in-process franken-engine and surfaces the program's real console output (stdout/stderr) plus an exit code derived from the runtime's containment verdict (91 Challenge, 92 Sandbox, 93 Suspend, 94 Terminate, 95 Quarantine).run --jsoncarries the engine's reasoning indispatch.engine_decision: the MAP risk state and posterior, the expected-loss selector's action, the stopping rule that crossed, and the instruction count; human output prints the same as awhy:line under the containment note. A program that throws still prints what it printed before the throw, then exits non-zero, as under Node (run --jsondoes not yet carry that output). An entry runs as an ES module when it is.mjs/.mts, or its nearest package scope declares"type": "module". Untyped.js,.ts, and extensionless entries use the engine's syntax detection after the worker admits and reads their source; for example, anexportdeclaration selects an ES module without requiring a package type. Explicit.cjs/.ctsand"type": "commonjs"keep CommonJS semantics. Detection honors the configured parser budget and keeps the original TypeScript/hashbang ingestion evidence. Explicit formats avoid the additional detection parse. A CommonJS entry hasrequire,module,exports,__filenameand__dirnameand canrequire('./lib/x')files inside the project directory, whilerequire('fs'),require('path')and the other supported builtins keep routing through the capability-gated host calls.require('pkg')resolves installed packages the way Node does (thenode_moduleswalk-up inside the project directory, package.jsonexportswith therequirecondition,main, and.jsonmodules). Retained probes of six larger packages (lodash, ms/semver, dayjs, uuid, commander, minimist) did not match Node. Three pinned tiny packages (is-number,isarray,escape-string-regexp) now have offline upstream-source specimens and match published v0.2.0, Node and Bun in all three profiles; that is release evidence, not current- source or representative ecosystem qualification. Large files can exceed the per-file parse budget, some syntax still fails to parse, string literals mentioning "token" or "password" are refused by the information-flow check, and some builtins still exist only as recognized call patterns such asfs.readFileSync(...). The native providers listed below also expose runtime objects and functions that packages can pass around (bd-reality-20260923-26n9r.4). With--frozen-modules, captured packages can use literalrequire, ESMimport, reexports andimport()for the engine's nativebuffer,events,os,querystring,url,timers,timers/promises,perf_hooks,path,path/posix,path/win32,util,util/types,assert,assert/strict,string_decoder,stream,stream/promises,module,cryptoandzlibproviders, includingnode:aliases. ESM default exports share the CommonJS objects, and named exports come from the same native providers. Each route retains its policy and information-flow checks, including cached loads. This does not provide the complete Node/Bun builtin API inventory. TypeScript enums and prefix assertions such as<number>valuein frozen.ts,.mtsand.ctsmodules use the engine's existing normalizer. Assertions erase types without converting runtime values; their type-only imports never grant filesystem access. Initializer and asserted-expression dependencies remain in the pinned graph, and prepared JavaScript must preserve those routes and obey the configured parser budget. JSX/TSX syntax, namespace, parameter-property and import-equals transforms remain outside frozen-module admission. Frozen capture also supports immutable top-level request strings such asconst target = './adapter.cjs'; require(target), earlier constant aliases, string concatenation, templates, and bounded conditional string choices. The same captured targets work withrequire.resolve,import()and supportedcreateRequirebindings. Capture includes every possible string branch; it does not execute conditions or predict the branch that will run. Mutable, shadowed, forward-referenced and otherwise unproved bindings remain refused, and these expressions grant no access to uncaptured modules. ESM packages can construct a captured loader withcreateRequire(fileURLToPath(import.meta.url)). Named, renamed, default and namespace imports fromurlornode:urlare supported, as are earlier top-levelconstfilename aliases. Capture proves the converter and origin bindings; runtime still requires the exact current captured filename and authorizes every dependency request. Arbitrary URL inputs, converter mutation, shadowing and unproved filename aliases remain refused. CommonJS packages can also userequire.main === moduleand the equivalent strict equality/inequality checks through proven loader aliases to distinguish CLI execution from a dependency load. Capture retains dependencies from both branches; this support does not expose arbitrary loader properties or allow the loader object to escape. Thecryptoprovider exposes the engine's existing hash/HMAC, key-derivation, cipher, key-pair/signature and random functions as ordinary callable exports; fresh entropy still requiresrandom_readat invocation. Thezlibprovider exposes its existing bounded gzip/deflate/Brotli operations, including the supported callback APIs. Extracting these functions or passing them through another package retains their capability, information-flow and resource checks. Frozen module execution can admit contained workspace links withrun app.cjs --frozen-modules --frozen-module-symlinks, or with bothruntime.frozen_modules = trueandruntime.frozen_module_symlinks = truein configuration. Capture rejects links that escape the project, absolute targets, and cycles; the default continues to reject module symlinks.Select custom package
exports/importsbranches with repeatable conditions:franken-node run app.mjs --conditions development --conditions react-server(short form-C). This works for both ordinary and frozen native loading. Each load retainsnodeplus its ownimportorrequirecondition; package declaration order still decides which matching branch wins. The equivalent TOML setting isruntime.module_conditions = ["development", "react-server"]. Explicit CLI conditions replace the configured list; the environment override accepts a JSON array. At most 62 nonempty names of 128 bytes each are admitted, with control characters rejected. Conditions select code without granting additional capabilities. Frozen captures and offline replay retain the condition set, so changing it changes the source-graph commitment.Source builds also accept an independently retained source-graph pin:
run app.cjs --frozen-modules --frozen-module-graph-hash "$REVIEWED_SOURCE_GRAPH_HASH". The equivalent configuration isruntime.frozen_module_graph_hash = "sha256:..."withruntime.frozen_modules = true; an explicit CLI pin takes precedence. Obtain the fullsha256:plus 64 lowercase hexadecimal digits without running the guest through the product CLI:franken-node verify source-graph ./project app.cjs --json # Review and retain input_hash as REVIEWED_SOURCE_GRAPH_HASH, then: franken-node run ./project/app.cjs --frozen-modules \ --frozen-module-graph-hash "$REVIEWED_SOURCE_GRAPH_HASH"The inspection uses the same bounded source capture as native frozen execution and emits its existing
franken-node/module-source-graph/v2metadata report, without source bodies, dependency installation, network access or guest evaluation. Exit 0 means the declared static request scope is complete; incomplete captures retain diagnostics and exit 1. Runtime completeness and release certification remain false. Use the same project root and entrypoint for inspection and execution. Native file execution chooses its root from the selected path: an absolute entrypoint uses its parent; a relative entrypoint searches for the nearestpackage.jsonorfranken_node.tomlbetween its parent and the invocation directory, falling back to its parent. For a nested packaged entrypoint, inspect the package root and run the relative entrypoint from that root. Switching that nested path to an absolute path changes the admitted root and can invalidate the reviewed pin. If inspection needs--allow-contained-symlinks, execution also needs--frozen-module-symlinks. The developmentfranken-module-graphtool exposes the same capture through--capture-source-graph. Pass the same repeatable--conditionsvalues toverify source-graphandrunwhen reviewing a custom-condition graph. The development inspection tool calls this additive option--additional-condition; its existing--conditionoption continues to replace the complete condition set. Keep that approval outside guest-writable source; a configured pin needs an operator-controlled TOML file. The native worker checks the pin before preparing or evaluating modules, then executes that same captured graph. Changed captured source, observed manifests, links or resolution probes require a newly approved pin, even when console output would be identical. A graph pin is operator-selected source integrity; it supplies neither publisher provenance nor additional capabilities. Files the graph never observes are outside this commitment: for example, a.cjsentry with explicit relative.cjsloads need not read an unused rootpackage.json. Current installed-release qualification remains a separate requirement.Publisher-approved frozen source. The product can also bind this graph to an existing signed local registry artifact. Export the exact captured inputs as a new private capsule, then publish those bytes with the publisher's signing key. Source export never executes the program or overwrites an existing file:
franken-node verify source-graph /projects/example app.cjs \ --write-source-capsule /review/example.capsule --json franken-node registry publish /review/example.capsule \ --version 1.2.3 --signing-key /operator/publisher.ed25519 --json # Retain the absolute manifest_path from the publish result, then from the project: franken-node run app.cjs --frozen-modules \ --registry-artifact /registry/path/artifact.manifest.json \ --publisher-public-key /operator/publisher.pubSupply a publisher public key independently retained outside the guest project; an embedded key cannot approve itself. The registry artifact must be a complete
FNSGCAP1source capsule within the registry's 5 MiB artifact limit. Both the dispatcher and its authenticated native worker reverify the exact retained manifest and capsule using this external key. The worker then checks the live graph against that signed content and executes its single immutable capture. Changed entry/dependency bytes, observed manifests or probes are refused before evaluation. The entrypoint, additional conditions and contained-symlink policy must match the capsule, and an explicit--frozen-module-graph-hashmust agree with it. Manifest, artifact and key reads are bounded and reject symlinks in every path component.Native-enabled builds also export and admit capsules containing supported Node builtins and captured failures for optional
require()orimport()requests. These source-free terminal routes use the same admission rules as the native loader. The source report retainsfully_resolved: false; successful explicit export addscapsule_closure_verified: true. Missing static imports, unknown builtin providers and unbounded computed loads remain refused. A dependency that appears after capture changes the graph pin and requires new approval. Capsule admission grants no capabilities and does not replace the worker's source preparation or current policy checks.The frozen graph report and run receipt retain the authenticated publisher key ID, package name/version and artifact hash. The
native_module_graphreceipt field participates in both the receipt hash and its deterministic identity; the original signed run record authenticates the same receipt snapshot. Publisher-approved runs require that signed record to be persisted, even without--capture-replay. Missing or misconfigured receipt signing authority, or failed record persistence, fails the command. This check occurs after execution and does not undo guest effects.This establishes publisher approval of source identity and a durable statement from the run receipt authority. It does not authenticate unsigned registry lifecycle/provenance fields, attest registry-service approval, grant capabilities or increase resource limits. Existing dependency trust-card revocation and quarantine, fleet policy and runtime controls still govern execution. The broader signed capability and resource contract remains separate work.
The broader host-effect "runtime-of-record" — the full Node/Bun host-API surface with capability-metered effects — is still in active development under the engine-split program; do not yet rely on it as a complete drop-in for arbitrary host-effect workloads.
incident replayverifies the recorded incident by default. Explicit--executere-executes source and host-I/O captured withrun --capture-replay. Completed guests and certified uncaught-exception prefixes have distinct verification scopes; a failed prefix has no completed IR4 witness or final instruction-count/nondeterminism comparison. Explicit frozen-module source capture is implemented but awaiting current-source native qualification; entry-only captures still disable runtime module loading. Ambient environment replay and interrupted execution remain unsupported. Admitted process replay preserves captured preparation and global effect order while refusing live process dispatch. Original policy decisions are not certified by the guest replay verdict.Source builds require the sibling
franken_enginerepository to be checked out next to this one (engine-split contract). The one-line installer side-steps this by shipping prebuilt binaries.Homebrew is not currently published. Use the installer or build from source.
FAQ
Is this a drop-in replacement for Node or Bun?
No. The measured compatibility corpus is the L1 spec. Its artifact of
record says 69.82% (391/560, artifacts/13/compatibility_corpus_results.json);
a 2026-09-25 re-measurement on one host measured 78.93% under the default
balanced profile, which refuses file writes and process.platform by
policy, and 86.61% under legacy-risky (see CLAIM-001 for provenance
limits). child_process is 0/30 in every profile: native-eval aborts
remain fail, not pass.
verify lockstep defaults to a Bun+franken dyad for hosts without real
Node.js; that dyad is not the spec. When --runtimes includes node as
real Node.js, Node is the spec and matching only Bun stays fail. Inspect
divergence receipts before any production rollout.
Does franken-node require a full rewrite of existing projects?
No. The migration autopilot audits and transforms incrementally, emits
unsigned JSON rollback plans (--emit-rollback, not Ed25519-signed), and
runs static+smoke migrate validate. Behavioral comparison is a separate
verify lockstep stage.
Can I run franken-node without a centralized fleet control plane?
Yes. Default fleet state is the local durable store under
.franken-node/state/ (live_control_plane=false). Multi-node
coordination is opt-in: run franken-node fleet serve on one node and
set [fleet] control_plane_url (plus control_plane_token_path) on the
others.
What does deterministic replay include?
Replay bundles include the workflow timeline, environment snapshot, policy decisions, trust artifacts referenced by those decisions, the evidence ledger entries for the incident window, and signed schema-version metadata so the bundle can be reproduced exactly under the same runtime version.
How does franken-node help with supply-chain risk?
It combines Ed25519-signed artifacts, provenance checks, revocation
freshness gates, trust cards (with camouflage and fragility signals), and
local file-transport quarantine (live_control_plane=false) as runtime
defaults rather than external scanners. Risky and dangerous actions
consult fresh trust state before they execute.
Do I have to use strict mode?
No. balanced is the default for most teams. strict is intended for
high-assurance environments and post-incident lockdowns; legacy-risky is
intended only for constrained migration windows where insecure compatibility
behaviors must be explicitly enabled by policy.
What is a "trust card"?
A per-extension record carrying publisher identity, risk assessment, audit
history, version chain, vulnerability state, camouflage assessment, and
revocation status. Cards are HMAC-signed snapshots loaded under a
SnapshotSourceContext (trusted-file vs. untrusted-network, which picks
the validation strategy), and consulted by the runtime before risky and
dangerous actions.
What is the "counterfactual simulator"?
After an incident, franken-node incident counterfactual --bundle <b> --policy strict replays the same captured trace under a different policy
profile and reports the diff of decisions, blocked actions, and evidence
emissions. It answers "would strict mode have caught this?" with a
reproducible artifact.
Where do I find the public verifier SDK?
The crate frankenengine-verifier-sdk lives at
sdk/verifier/ and is deliberately smaller and more
stable than the main product crate. It exposes bundle, capsule, and
counterfactual surfaces for third parties verifying claims independently
of the runtime that produced them.
How are CI gates organized?
Gate-oriented and path-scoped. Each contract has a docs/specs/<gate>.md,
a scripts/check_<gate>.py validator, and one or more
.github/workflows/*-gate.yml workflows that run the validator on changed
paths. Closer-discipline, no-contract-no-merge, claim gates (ATC, BPET,
DGIS, VEF), connector conformance, replay coverage, canonical vectors,
mutation testing, and security golden artifacts are all enforced.
How does franken-node prevent SSRF and DNS rebinding?
Every outbound network call guest code makes on the native engine path
(the default for run) passes through the SSRF policy gate
(security::ssrf_policy). The node/bun fallback runtimes, which need the
explicit FRANKEN_NODE_ALLOW_DEGRADED_RUNTIME_FALLBACK opt-in, get policy
hints only and are not gated. The gate enforces CIDR-aware deny rules
(including private ranges and loopback by default), checks per-connector
allowlist entries with optional port pinning, and re-validates resolved
IPs after DNS resolution to defeat rebinding.
[security.network_policy].ssrf_enforcement is block (default),
monitor or none. monitor currently enforces like block on the
native run path (no allow-and-record mode yet); none disables the gate. Capability tokens are
endpoint-bound, so a token issued for https://api.example.com cannot be
silently redirected to http://127.0.0.1:8080.
What happens when the runtime enters safe mode?
franken-node safe-mode enter --reason <reason> --operator-id <id> --trust-state-hash <hash> persists unsigned operator state under
.franken-node/safe-mode/state.json and blocks new application runs whose
canonical entrypoint is in this directory or any descendant. Admission checks
run in the CLI, the public dispatcher, and again in the authenticated native
worker before source execution. An inactive child scope cannot override an
active ancestor; malformed, oversized, unreadable, and nonregular state also
refuses admission. The error identifies the controlling scope, where the
operator should inspect and recover state.
Already running native sessions retain their admitted entrypoint scopes and recheck local safe mode, dependency trust, and fleet policy every 250 ms between completed checks. A disallowed state interrupts the worker and its containment unit. A fleet update that changes effective instruction, parser, or network restrictions requires restarting the guest under that policy; equivalent newer revisions continue while advancing the observed revision floor. Applications are not automatically restarted. Renaming an already loaded entrypoint does not change its retained safe-mode scope.
Control reads run on one owned observer per session, independently of the
worker's absolute deadline. If a check cannot complete for 10 seconds, execution
fails closed. The worker is terminated before the supervisor joins an observer
still finishing a store read, so returning the result can take longer than
stopping the guest. run --json reports control_trigger and
control_indeterminate (or control_cleanup_unproven if cleanup fails), with
the actual interrupted-effect WAL and a persisted unsuccessful-run receipt.
In-flight provider outcomes remain unknown; containment does not undo effects
already committed. Remote fleet propagation still depends on agent polling,
and a fleet action acknowledgement proves persistence rather than session
quiescence. Live-control interruptions do not increment the panic counter.
The native supervisor automatically enters safe mode after three validated
native engine panic responses for the same entrypoint within 60 seconds. It
records the immutable session nonce and original observation time in the
resolved project root's controller; the controller's crash_loop_threshold
and crash_loop_window_secs configure those defaults. Guest exceptions,
timeouts, and generic worker exits do not contribute to this panic count.
These are parent-observed panic records; the separately signed session-key
capture does not sign the panic outcome itself. This performs containment,
not automatic rollback to an earlier application artifact.
Concurrent crash and operator updates share a stable sibling lock and replace
the controller atomically after syncing a temporary file. Explicit recovery
retires recorded crash contributions while retaining recent nonce identities,
so replaying an old observation cannot immediately reactivate safe mode.
Expired observations keep their original times and remain uncounted. If the
clock moves backward, the tracking configuration is invalid, or retaining
recent identities would exceed the bounded history, a distinct
CrashTrackingFailure reason activates safe mode. A storage failure is
attached to the original panic error; it is never reported as a successful
counter update. Panic errors also expose this result through run --json.
trust-corruption and epoch-mismatch still require an operator-supplied
reason, and inspection commands remain available. A different in-flight panic first
recorded after recovery can start the fresh window.
Exiting requires franken-node safe-mode exit --confirm --operator-id <id>
plus the operator attestations (--trust-state-consistent,
--no-unresolved-incidents, --evidence-ledger-intact), which are not
independently verified by the CLI. A custom --state-dir is standalone unless
it addresses one of these conventional ancestor state locations. This local
operator control is unsigned and does not protect against an actor permitted
to replace the operator's state files.
How do I scale up to many fleet nodes?
Default fleet state is the local durable store, not a live cluster.
To span nodes, run franken-node fleet serve --generate-token on a
coordinator, copy the token file to every node, and set
[fleet] control_plane_url / control_plane_token_path (or
FRANKEN_NODE_FLEET_CONTROL_PLANE_URL /
FRANKEN_NODE_FLEET_CONTROL_PLANE_TOKEN_PATH). franken-node fleet agent --zone <zone> then polls the coordinator and heartbeats into it.
convergence_timeout_seconds governs how long fleet release and fleet reconcile wait for convergence before failing closed.
What is the difference between runtime.lanes and SchedulerLane?
SchedulerLane is the four-lane logical priority structure built into
the runtime (ControlCritical, RemoteEffect, Maintenance,
Background). runtime.lanes is the user-facing TOML table of named
lane configs (e.g. cancel, realtime) that parameterize concrete
bulkheads. Task classes are mapped to named lanes via mapping rules,
and named lanes inherit the priority order of their containing
SchedulerLane. Most operators only configure the named lanes; the
four logical lanes are an internal invariant.
What's vectors/CHANGELOG.md for?
Per-vector test data (golden inputs, canonical fixtures, conformance
seeds) has its own changelog at
vectors/CHANGELOG.md. Vector bumps are
governance events because they cross-reference signed conformance
artifacts; keeping their history separate from the main CHANGELOG.md
lets operators audit golden drift independently of code drift.
Where do I find runbooks for specific failure modes?
Operator runbooks live under docs/runbooks/. The proof-lane
readiness blocker runbook
(docs/runbooks/proof_lane_readiness_blockers.md)
and the recovery runbook generated by franken-node verify recovery-runbook
are the two most-used entry points.
About Contributions
About Contributions: Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
License
MIT License with OpenAI/Anthropic Rider. This is not permissive MIT: the
rider grants no rights to OpenAI, Anthropic, their affiliates, or anyone acting
for them, and prohibits incorporating the software (or any derivative work) into
any dataset, training corpus, evaluation harness, or ML pipeline. See the full
terms in LICENSE.
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
562,128 starspublic-apis/public-apis
A collective list of free APIs
498,513 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
455,100 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,410 stars