The shape of the engine — crates, boundaries, primitives, invariants, and how a request flows through the layers.
For plain-English orientation, start with HOW-IT-WORKS.md. This doc assumes you want depth.
The Rust workspace ships fifteen Rust crates plus the napi bindings + the
TypeScript DSL wrapper. The 8 → 10 crate transition completed in
Phase 3 — benten-id (9th, identity + claims) and benten-sync
(10th, sync runtime — native-only) landed and were filled in across
Phase 3's implementation cluster. Phase 4-Foundation extended the
workspace to twelve crates per the post-R1-triage ratification
(r1-triage.md §1 ratification #1):
benten-platform-foundation(11th — schema-driven rendering compiler + materializer pipeline + plugin manifest +Renderertrait abstraction. This crate is intentionally broader than other crates because it's the v1 platform-shippable surface — narrower three-or-four separate platform crates rejected per arch-r1-8 closure).benten-renderer-tauri(12th — Tauri 2.x renderer engine extension per CLAUDE.md baked-in #19; compile-time linked; user trust = "you compiled this in"; distinct from app-level plugins which are subgraphs).
Phase 4-Meta-Core G-CORE-2 (#1300) extends the workspace to thirteen
crates by adding benten-crypto-suite — the ONE thin Benten-owned
signature / hash / cipher-suite agility integration crate (the ONLY
crypto-primitive call site per RATIFIED-crypto-agility-2026-05-18.md
line 6; v1-beta signature default = hybrid Ed25519⊕ML-DSA-65; concat /
hash / codepoint / envelope glue over vetted upstream RustCrypto
crates).
Phase 4-Meta-Core G-CORE-3f extends the workspace to fourteen
crates by adding benten-drop — the Drop bundle format crate
(self-contained CBOR-on-disk envelope shipped over the iroh-blobs
two-CID seam; Mode 2 sendme→Drop ships at G-CORE-3f; Mode 3
inline-tiny deferred to post-v1 per .addl/phase-4-meta/00-implementation-plan.md
§3 G-CORE-3 def input-constraints refinement #6 L341). Carries
Spike G's envelope-sig + per-Node-sig defense-in-depth (<12%
overhead). Per RATIFIED-sharing-and-confidentiality-2026-05-21.md
§R6: Drop bundles are forever-valid once distributed (the
revocation-reach asymmetry between online-pull and offline-Drop is
documented at docs/SECURITY-POSTURE.md § "Revocation reach").
Phase 4-Meta-Core F-full extends the workspace to fifteen crates by
adding benten-membership-set — the MembershipSet keying primitive (a
thin keying-glue Rust engine plugin: the EXACTLY-3 MembershipSetKind
enum + its 0x6600/0x6610/0x6620 codepoint band + the
multi-stanza-HPKE keying glue + the members_table canonical-CBOR
snapshot + the 0x6610 group AAD = the BLINDED 11-field set + the
5-value RoleId RBAC axis + the Inv-21 fork-tie-break CRDT rule). It
delegates every crypto primitive to benten-crypto-suite (NEVER forks
#5) and depends UPSTREAM on benten-sync for CRDT/HLC/MST/transport;
governance / audit / federation / economics live as GRAPH (data-half).
Native-only per CLAUDE.md baked-in #17 (its dep set includes the
native-only benten-sync). Owns Inv-19..22. See
docs/V1-FROZEN-INTERFACE.md §16 for the frozen public surface.
The narrative below is the post-F-full shape.
crates/
benten-errors/ # Stable ErrorCode discriminants. Zero Benten-crate deps.
# Root of the workspace dependency graph.
benten-core/ # Node, Edge, Value. Content-addressed hashing
# (BLAKE3 + DAG-CBOR + CIDv1). Version-chain primitives.
benten-graph/ # Storage (KVBackend trait + redb impl), indexes, MVCC
# via redb transactions.
benten-ivm/ # Incremental View Maintenance. Subscribes to graph
# changes; updates materialized views. Not known to
# the evaluator.
benten-caps/ # Capability grants as Nodes. Pre-write hook trait.
# NoAuthBackend default; GrantBackedPolicy ships alongside.
benten-eval/ # 12 operation primitives. Iterative evaluator (explicit
# stack, not recursive). Structural validation (14
# invariants). Transaction primitive.
benten-id/ # 9th crate (Phase 3). Ed25519 keypair
# management with secret-key zeroization on
# drop, did:key DID generation (z-multibase
# prefix + 0xed01 multicodec per W3C spec),
# UCAN claim envelope + chain validation +
# nbf/exp time-window enforcement, VC issuance
# / verification with `credentialStatus`
# revocation, DID rotation with `superseded_by`
# attestation chain, MultiSigSurface trait
# (Ed25519SingleKey default impl + threshold
# extension point), and the device-DID
# capability-attestation surface (a pure
# signed-ceiling primitive; COLLAPSE P3 deleted
# the Acceptor acceptance pipe — stale-frame
# replay is bounded by a freshness window on
# the consuming Atrium handle). Dependency edges:
# `ed25519-dalek`, `ssi`, `blake3`,
# `serde_ipld_dagcbor`, `zeroize`, `secrecy`,
# `subtle`, `getrandom` only — NO edges back to
# `benten-graph` / `benten-eval` /
# `benten-engine`.
benten-sync/ # 10th crate (Phase 3). Atrium P2P sync layer:
# iroh transport (loopback + relay + holepunch
# fallback) behind the `transport_trait::
# Transport` abstraction boundary (RATIFIED
# §15.3 #1; `IrohTransport` is the pre-v1
# concrete impl, post-iroh swaps land as
# engine extensions per CLAUDE.md #19), Loro
# CRDT for per-property LWW
# with HLC ordering, MST diff for delta
# computation, DID handshake protocol with
# mutual auth, and the `host:atrium:*`
# capability surface (`publish_view_result`
# UCAN-gated — view-result replication does NOT
# introduce a new trust-policy primitive).
# NATIVE-ONLY: the crate is excluded from
# `wasm32` targets so browser tabs and edge
# thin-compute deployments do not carry iroh /
# Loro in their bundles. Browser / edge
# surfaces sync THROUGH a full peer via
# authenticated thin-client protocol; they are
# not full Atrium peers themselves.
benten-dsl-compiler/ # Compiles the textual handler-DSL grammar
# into benten_core::Subgraph. Phase-2b addition;
# routes from devserver into
# Engine.register_subgraph.
benten-engine/ # Composes the above into a public API. Wires
# the capability hook, storage backend, IVM
# subscriber. Phase 3 added the Atrium DSL
# session-handle
# `engine.atrium({config}).join()`.
benten-platform-foundation/
# 11th crate (Phase 4-Foundation). The v1
# platform-shippable surface. Hosts the
# schema-driven rendering compiler
# (typed-field-Node vocabulary → renderable
# SubgraphSpec), the materializer pipeline
# (HtmlJsonMaterializer + IVM-subgraph
# generalisation), the full plugin manifest
# surface (install-time consent + per-plugin
# DID + manifest envelope chain validation +
# private namespace caps + DAG-shape
# versioning), the admin UI v0 plugin
# subgraph + 4-category navigation IA, and
# the `Renderer` trait abstraction with
# `BrowserRender` (browser-wasm32) as default
# impl. Per arch-r1-8 closure, the crate is
# intentionally broader than other crates
# because every part of it composes into one
# platform-shippable boundary.
benten-crypto-suite/ # 13th crate (Phase 4-Meta-Core / G-CORE-2).
# The ONE thin Benten-owned signature / hash /
# cipher-suite agility integration crate. Per
# `RATIFIED-crypto-agility-2026-05-18.md` line 6:
# the ONLY crypto-primitive call site in the
# workspace. Concat / hash / codepoint / envelope
# GLUE only over vetted upstream RustCrypto
# crates (`ed25519-dalek`, `blake3`, `sha2`,
# `sha3`, `subtle`, `zeroize`); the integration
# crate NEVER forks or reimplements primitives
# (CLAUDE.md baked-in #5). v1-beta signature
# default = hybrid Ed25519⊕ML-DSA-65
# (byte-faithful IETF LAMPS composite
# id-MLDSA65-Ed25519-SHA512; both halves MUST
# verify; NO commitment trailer); classical-only
# Ed25519 is the
# non-default downgrade arm. Unknown / reserved
# codepoints surface typed
# `UnsupportedAlgorithm` — NEVER a silent
# fallback (CLAUDE.md baked-in #5 +
# `RATIFIED-pq-default-reframe-2026-05-19.md`
# §4 P2P-interop invariant). The live ML-DSA-65
# primitive instantiation is deferred to the
# coordinated workspace dep-bump wave alongside
# G-CORE-3 #1301 (see the Cargo.toml record);
# the hybrid arm typed-rejects until then. The
# safety invariant is preserved: unaudited PQC
# is never the SOLE trust path; the classical
# half is the audited security floor.
benten-drop/ # 14th crate (Phase 4-Meta-Core / G-CORE-3f).
# Drop bundle format — self-contained CBOR-on-
# disk envelope `DropBundle{version, spec_cid,
# auth_grant, content: Vec<EncryptedContent>,
# restricted_spec, per_node_attestation,
# envelope_sig, issuer_verifying_key}` shipped
# over the iroh-blobs two-CID seam. Mode 2
# (sendme→Drop) ships at G-CORE-3f; Mode 3
# (inline-tiny) deferred to post-v1 per
# `.addl/phase-4-meta/00-implementation-plan.md`
# §3 G-CORE-3 def input-constraints refinement
# #6 L341. Carries Spike G's envelope-sig +
# per-Node-sig defense-in-depth (<12% overhead;
# envelope-sig binds header only, AEAD tags
# bind content — two independent integrity
# layers). Consumer reads filesystem-only (no
# network; no live publisher). Per RATIFIED-S&C
# §R6 revocation-reach reality: Drop bundles
# forever-valid once distributed (mitigations:
# tight nbf/exp + periodic key rotation;
# documented at SECURITY-POSTURE.md
# "Revocation reach"). Dependency edges:
# benten-core (Cid), benten-caps
# (AuthorizationGrant + RestrictedScope +
# UcanEnvelope), benten-graph
# (EncryptedNode + encode/decode), benten-id
# (Keypair for envelope-sig), benten-crypto-suite
# (BLAKE3 + Ed25519 via the only-call-site
# primitive re-exports per CLAUDE.md #5).
benten-membership-set/ # 15th crate (Phase 4-Meta-Core / F-full). The
# MembershipSet keying primitive — a thin
# keying-glue Rust engine plugin. Owns: the
# EXACTLY-3 `MembershipSetKind` enum (Atrium /
# DeviceMesh / SingleDevice) + the
# `0x6600`/`0x6610`/`0x6620` codepoint band +
# the multi-stanza-HPKE keying glue + the
# `members_table` canonical-CBOR snapshot +
# `assemble_group_aad` (the `0x6610` group AAD =
# the BLINDED 11-field set, big-endian) + the
# 5-value RoleId RBAC axis + the Inv-21
# fork-tie-break CRDT rule. Delegates EVERY
# crypto primitive to benten-crypto-suite (NEVER
# forks #5). Depends UP on benten-sync for
# CRDT/HLC/MST/transport. Native-only (CLAUDE.md
# #17). Governance/audit/federation/economics are
# GRAPH (data-half). Owns Inv-19..22. See
# V1-FROZEN-INTERFACE.md §16.
benten-renderer-tauri/
# 12th crate (Phase 4-Foundation). Tauri 2.x
# renderer ENGINE EXTENSION per CLAUDE.md
# baked-in #19: compile-time linked Rust crate
# implementing the `Renderer` trait for
# embedded-webview deployment shape (c).
# Single typed `IPC_METHODS` binding slice
# (name + `CapRequirement`; co-located so the
# allowlist-to-cap-binding bijection is
# structural, not test-guarded) + locked CSP +
# gate-only `dispatch_ipc`. T3 rung-2 fails
# CLOSED by construction (no empty-string
# sentinel, no fail-OPEN fallback). In-process
# IPC protocol. Trust = "you compiled this in."
# NOT an app-level plugin subgraph.
bindings/
napi/ # Node.js bindings via napi-rs v3. Compiles to a
# native dynamic library (`.node`) for desktop /
# server AND to a wasm32 module for browser / edge
# runtimes. The wasm32 build is a thin compute
# surface: stateless reads against snapshot data
# + writes via fetch to a full peer; no Loro /
# iroh / direct sync state in the bundle.
packages/
engine/ # TypeScript DSL wrapper (@benten/engine). Phase 3
# adds the Atrium walkthrough surface (peer connect /
# sync trigger / UCAN grant flow / DID resolution)
# over the napi surface.
A workspace test pin verifies all fifteen crate names + the
native-only annotation on benten-sync are present in this document
(see crates/benten-engine/tests/architecture_md_12_crate_count_post_phase_4_foundation_canaries.rs),
so the Phase-4-Meta-Core-F-full shape described above is the durable
narrative.
The crate graph is DAG-shaped:
benten-errorshas no Benten dependencies; every other crate imports from it for error discriminants.benten-iddepends only on third-party crypto crates (no edges back to graph / eval / engine), so the identity surface is reusable outside the engine if needed.benten-syncdepends onbenten-id(DID + UCAN) andbenten-core(Node + content-addressing) but NOT onbenten-eval— sync is not allowed to walk the evaluator.benten-enginesits at the top and is the only crate applications link against.
Each crate has one responsibility. A reader can use benten-engine with NoAuthBackend and no IVM subscribers as a content-addressed embedded graph DB, with none of the capability, sync, or application machinery engaged. Features that can live outside the evaluator's main loop are moved into sibling crates.
Beyond the fifteen Rust crates, the workspace ships two ancillary trees that exist
to make the engine reachable from JavaScript and to keep developer onboarding
ten minutes from npx to a green test:
bindings/napi/— Node.js bindings vianapi-rsv3. The same Rust codebase compiles to a native dynamic library (.node) for desktop / server and to a WASM module for browser / edge runtimes. Auto-generates TypeScript.d.tsfiles from#[napi]annotations so the TS DSL never hand-writes a binding signature. Phase-2a surfaces here includecall_with_suspension,resume_from_bytes,resume_from_bytes_as,grant_capability,revoke_capability, and the trace + diagnostics APIs.
tools/create-benten-app/— thenpx create-benten-app <name>scaffolder. Drops a minimal TypeScript project (handler file + smoke test +tsconfig.json) wired to@benten/enginevia afile:link to the workspacepackages/engine. Closes the Phase-1 zero-config DX promise. Templates live undertools/create-benten-app/template/.tools/benten-dev/— the diagnostic CLI. Theinspect-state <path>subcommand reads a DAG-CBORExecutionStateEnvelopefrom disk and pretty-prints the suspended state.tools/benten-admin-shell/— Phase-4-Foundation R6-FP-E integrator binary for deployment shape (c). Production caller forbenten-renderer-tauri'sTauriRenderer+InProcessSessionBridge; composes them with the canonical admin-UI-v0 manifest envelope intoAdminShellState::dispatch. Default-mode boot prints the IPC method- cap-binding map + locked CSP header. With thetauricargo feature enabled,tauri_boot::runwires the REAL Tauri 2.x runtime throughBuilder::default().invoke_handler(...).run(generate_context!()); the webview-driventauri-driverE2E attools/benten-admin-shell/tests/e2e_webview_smoke.rsdrives a real Tauri command-invoke roundtrip + CSP eval-block assertion via fantoccini-rustls WebDriver client (linux substantive, macos build- only smoke per upstream WKWebView limitation, Windows deferred perdocs/future/phase-4-backlog.md §3.6). Default-mode substantive pin attools/benten-admin-shell/tests/e2e_admin_shell_ipc.rsexercises the T3 three-rung defense + bridge resolve through 9 happy-path + negative-arm cases. Closes R6 R6-R1 br-r6-r1-3 BOTH halves (path-a-FULL).packages/engine-devserver/— the napi-rs-backedBentenDevServerTypeScript wrapper that wraps a realEngineand exposesreplaceHandler/ hot-reload semantics throughEngine::register_subgraph_replace. Phase 2b landedbenten-dsl-compilerand collapsed the prior parallel-infrastructure posture; see "Devserver path" below.
READ WRITE TRANSFORM BRANCH ITERATE WAIT
CALL RESPOND EMIT SANDBOX SUBSCRIBE STREAM
Each primitive is an Operation Node kind. The evaluator dispatches on PrimitiveKind to a per-primitive executor in crates/benten-eval/src/primitives/.
All 12 primitives have live executors as of tag phase-2b-close (2026-05-03). The "Landed in" column tracks the phase that first wired the executor; every row is in production-runtime use today.
| # | Primitive | Purpose | Landed in |
|---|---|---|---|
| 1 | READ | Retrieve a Node by CID, label, or property | Phase 1 |
| 2 | WRITE | Persist a Node; version-stamps if versioning is enabled on its label | Phase 1 |
| 3 | TRANSFORM | Pure expression evaluation over Values | Phase 1 |
| 4 | BRANCH | Conditional routing (forward-only) | Phase 1 |
| 5 | ITERATE | Bounded collection processing | Phase 1 |
| 6 | WAIT | Suspend until signal or timeout; resume with DAG-CBOR state | Phase 2a |
| 7 | CALL | Invoke another registered subgraph | Phase 1 |
| 8 | RESPOND | Terminal: produce the handler's output | Phase 1 |
| 9 | EMIT | Fire-and-forget change notification | Phase 1 |
| 10 | SANDBOX | WASM computation, fuel-metered, no re-entrancy | Phase 2b |
| 11 | SUBSCRIBE | Reactive change notification (composition point for IVM) | Phase 2b |
| 12 | STREAM | Partial output with back-pressure (SSE, WebSockets, LLM tokens) | Phase 2b |
Phase-1 storage was forward-compatible with Phase-2 primitives: subgraphs containing WAIT / SANDBOX / SUBSCRIBE / STREAM Nodes passed structural validation under Phase 1 and round-tripped through storage, even though their executors were stubbed. That binary-compatibility property still holds — older serialised subgraphs continue to load — but the executor stubs are gone; every PrimitiveKind dispatch arm wires to a live runtime.
Validated at registration time or fired at runtime, depending on invariant:
- Entry/exit well-formedness.
- Max operation-subgraph depth.
- Max fan-out per Node.
- SANDBOX nest depth limit. (landed Phase 2b)
- Max total Nodes per subgraph.
- Max total Edges per subgraph.
- SANDBOX fuel budget. (landed Phase 2b)
- Iteration budget — multiplicative through CALL and ITERATE nesting. (landed Phase 2a)
- Type-safety of Value flows.
- Reachability from entry.
- System-zone Nodes are unreachable from user subgraphs. (Phase 2a registration + runtime enforcement; Phase 2b extended SUBSCRIBE-pattern validation)
- Terminal Node presence.
- Immutability — registered subgraphs are not rewritable. (Phase 2a 5-row firing matrix)
- Causal attribution — every evaluation step carries a principal / handler / grant chain. (Phase 2a threading)
All 14 invariants are enforced as of phase-2b-close. Invariants 1–3, 5–6, 9–10, 12 landed in Phase 1; Invariants 8, 11, 13, 14 in Phase 2a; Invariants 4, 7 in Phase 2b alongside the SANDBOX runtime. See INVARIANT-COVERAGE.md for per-invariant enforcer + test pins.
- Caller invokes
engine.call(handlerId, action, input). benten-enginelooks up the handler's subgraph CID, reads the subgraph, locates the action entry Node.benten-evalwalks from the entry Node, dispatching each Operation Node to its executor:- READ hits IVM views (O(1)) or falls through to
benten-graphon a miss. - TRANSFORM evaluates a pure expression.
- BRANCH routes on a condition.
- CALL invokes another registered subgraph.
- RESPOND terminates with the handler's output.
- READ hits IVM views (O(1)) or falls through to
- Reads flow without capability checks by default. Phase 2a extended check-read to the content path for evaluator-driven reads under the grant-backed policy.
- Same entry flow.
- The transaction primitive wraps all WRITEs: begin → operations → commit (or rollback).
- Each WRITE hits the capability hook. Denial aborts the transaction; no ChangeEvents fire.
- On commit: content is hashed into CIDs, version chains advance (where enabled), the audit sequence advances, ChangeEvents fire to the IVM subscriber.
- IVM updates materialized views whose subscription patterns match the changes.
A WAIT primitive suspends the evaluator, produces an ExecutionStateEnvelope (DAG-CBOR, content-addressed), and returns a SuspendedHandle carrying the envelope CID. The caller persists the envelope bytes.
At resume the engine runs a 4-step protocol before continuing evaluation:
- Payload integrity. The envelope is DAG-CBOR decoded; a mismatch with its declared
payload_cidfiresE_EXEC_STATE_TAMPERED. - Principal binding. The resumption principal is verified against the caller's claimed identity; mismatch fires
E_RESUME_ACTOR_MISMATCH. - Subgraph pin check. Each pinned subgraph CID is re-verified against the registered-handler table; drift fires
E_RESUME_SUBGRAPH_DRIFT. - Capability re-check.
CapabilityPolicy::check_writeis re-invoked against the persisted head-of-chain grant CID; mid-eval revocation firesE_CAP_REVOKED_MID_EVAL.
Only after all four steps pass does evaluation resume.
benten-graph exposes a KVBackend trait. RedbBackend is the Phase-1 implementation: ACID, MVCC (concurrent readers with single writer), crash-safe via copy-on-write B-trees. A future WASM implementation will fetch content-addressed Nodes from peer storage.
redb transactions wrap every put_node_with_context call. The Phase-2a Inv-13 firing matrix dispatches on the write's WriteAuthority (User / EnginePrivileged / SyncReplica): User re-puts of an already-persisted CID fire E_INV_IMMUTABILITY; privileged dedup paths return Ok(cid) without emitting ChangeEvents or advancing the audit sequence (a named compromise — the privileged dedup is a pure read on the backend even though it passes through the write API).
Content is serialized via serde_ipld_dagcbor — the IPLD subset of CBOR with canonical encoding (map keys sorted, no indefinite-length forms). CIDs are v1 with multicodec 0x71 (dag-cbor) and multihash 0x1e (BLAKE3).
benten-ivm subscribes to ChangeEvents from the storage layer and keeps views current. Phase 1 shipped five hand-written views covering the hot paths: capability resolution, content listings, change-event fan-out, principal resolution, and view-staleness tallies. Phase 2b production-registered Algorithm B (dependency-tracked incremental maintenance) with per-view strategy selection (Strategy::A / Strategy::B) at Engine::create_user_view. Phase 3 generalised Algorithm B beyond the 5 canonical view IDs: Algorithm::register(view_id, label_pattern, projection) (and the budget-aware sibling Algorithm::register_with_budget) instantiates a generic single-loop kernel (benten_ivm::algorithm_b::GenericKernel) for non-canonical view IDs keyed on (label_pattern, projection), with the AnchorPrefix selector lift shipping in register_user_view. The drift-detector proptest harness at crates/benten-ivm/tests/algorithm_b_drift_detector.rs runs incremental-vs-rebuild parity end-to-end (5 pins × 1 000 cases). The ContentListingView silent-fallback for user-defined Strategy::B views is RETIRED.
The evaluator does not know IVM exists. Views are materialized Nodes; reads hit them via the normal read path.
Callers extend IVM beyond the 5 canonical views by constructing a
UserViewSpec and calling Engine::register_user_view (durable) or
Engine::create_user_view (transient). The public surface:
UserViewSpec::builder()— fluent builder requiringid+input_pattern;strategydefaults toStrategy::B.Strategy::Ais reserved for the 5 hand-written Phase-1 views andStrategy::Reservedfor future algorithmic families (renamed fromStrategy::Cat G23-0a per arch-r1-14); both are refused at registration time with typed errors.UserViewInputPattern— two-variant selector vocabulary:Label(String)(every change event whose Node carries the matching label) andAnchorPrefix(String)(every change event whose anchor id starts with the given prefix). Canonical view ids requireLabeland are fail-loud onAnchorPrefix(E_VIEW_LABEL_MISMATCH).- Projection lives on the kernel side at
benten_ivm::algorithm_b::{Algorithm::register, Algorithm::register_with_budget}— the engine wraps the user-facing builder into a(view_id, label_pattern, projection)triple that instantiates the generic single-loop kernel (GenericKernel) for non-canonical ids. - Budget semantics:
register_with_budget(view_id, pattern, projection, budget: u64)caps per-update work; each matching write consumes one budget unit and exhaustion fires the budget-exhausted outcome.budget == u64::MAXis the effectively-unbounded sentinel. - Reads route through
Engine::read_view/Engine::read_view_with(ReadViewOptions); strict mode firesE_IVM_VIEW_STALEwhen materialization is behind the latest write.
The TS DSL surface mirrors the Rust shape one-for-one via
packages/engine/src/views.ts::validateUserViewSpec, with napi
round-tripping the field names. The drift-detector proptest at
crates/benten-ivm/tests/algorithm_b_drift_detector.rs runs
incremental-vs-rebuild parity end-to-end so generalised Algorithm B
is held to the same correctness bar as the canonical kernels.
benten-caps defines a pre-write hook trait:
fn check_write(&self, ctx: &WriteContext) -> Result<Decision, CapError>;The engine's default is NoAuthBackend — allows everything, zero overhead — so embedded single-user deployments don't pay for capability machinery.
A GrantBackedPolicy ships alongside: grants are Nodes with GRANTED_TO edges, attenuation is verified along the delegation chain, revocation is a Node write. Phase 2a added TOCTOU refresh at five points (transaction commit, CALL entry, every N iterations of ITERATE, WAIT resume, wall-clock boundary) with a dual monotonic + HLC clock source. Phase 3 landed UCANBackend — a durable UCAN-grant policy backend over benten-id's claim envelope + chain validation surface. UCAN grants attenuate on delegation, propagate revocations, and validate nbf/exp time-windows at chain-walk time; signature comparison is constant-time via subtle::ConstantTimeEq. Compromise #11 (IVM views per-row read-gate) closed end-to-end in Phase 3 via the label-hint extraction + delivery-side filtering composition.
The canonical fixture CID bafyr4iflzldgzjrtknevsib24ewiqgtj65pm2ituow3yxfpq57nfmwduda is stable across x86_64 Linux / macOS / Windows and ARM64 macOS. The .github/workflows/determinism.yml workflow computes it on every PR; drift is a merge blocker.
The developer surface ships in two layers as of phase-2b-close:
tools/benten-devis the diagnostic CLI; itsinspect-state <path>subcommand reads a DAG-CBORExecutionStateEnvelopefrom disk and pretty-prints the suspended state. It does not host a runtime.packages/engine-devserveris the napi-rs-backedBentenDevServerTypeScript wrapper that wraps the realEngine(Phase 2b landedbenten-dsl-compilerso devserver-side handler text now compiles tobenten_core::Subgraphand registers throughEngine::register_subgraphrather than running parallel in-memory infrastructure). Hot reload, grant preservation across reload, in-flight call ordering, suspension-handle survival across reload, and audit-sequence invariance on reload are exercised against the same Engine APIs production callers use.
The Phase-2a ReloadCoordinator / CallGuard concurrency machinery —
RwLock<HandlerTable>::write() serialising concurrent reload-bumps,
each in-flight call's Arc<HandlerVersion> snapshot keeping its
pre-reload version live for the duration of the call — survived the
Phase-2b cutover unchanged. That ordering shape is concurrency
coordination, not storage, and is orthogonal to whether the registry
is in-memory or engine-backed.
Everything the engine uses at runtime is in the graph: capability grants (CapabilityGrant Nodes with GRANTED_TO edges), registered handlers (content-addressed subgraphs, immutable once registered), IVM view definitions (ViewDefinition Nodes with a strategy property), user preferences, change-event queues. The engine does not read configuration from YAML or a config service; it reads Nodes.
Two distinct categories of "extensibility" — different shapes, different trust models, different surfaces. Both are first-class.
A plugin is a subgraph of operation Nodes — handlers, materializers, SANDBOX nodes, READ/WRITE/etc. — packaged as content-addressed graph: importable, shareable, replicatable, and editable across Atriums. There is no separate plugin runtime; the engine evaluator walks plugin subgraphs the same way it walks any other handler.
Identity. Each plugin has its own DID + an attenuated UCAN delegated by the user at install. Walks of plugin subgraphs run with the plugin's principal active; capability checks fire against the plugin's UCAN chain.
Trust model — three layers.
- User-as-root. Every cap chain traces back to a user-issued root. P2P plugin discovery (Phase 8) does not weaken this — the user-mint root is still the trust anchor.
- Install-time manifest. Two halves:
requires(caps the plugin needs) andshares(policy for what other plugins are allowed to receive). Both are signed by the plugin author so they cannot drift post-install. The user reviews the manifest and consents to the envelope, not to each runtime access. - Runtime delegation within the manifest envelope. Plugin A can delegate a UCAN to plugin B if and only if B's request fits A's manifest
sharespolicy. The CapabilityPolicy backend validates the chain at access-time: chain-traces-to-user-root + each delegation step fits source policy + requested cap is within attenuation envelope.
Engine-side surface. The evaluator's read pathway threads the active principal through Engine::read_node_as(principal, cid) — the public surface for any read attributed to a non-trusted principal. Engine internals (IVM, sync, view materialization, audit) reach the unchecked storage read via self.backend.get_node(cid) directly — the backend field + accessor are both pub(crate), so external crates physically cannot bypass the policy gate. Plugin authors do not call either path: they author graph nodes; the evaluator is the only caller of _as. Mirrors the existing Engine::call_as precedent. The four prior todo!() stubs in crates/benten-engine/src/engine_wait.rs (put_node + read_node_with_policy (renamed to read_node_as) + the test-only read-grant helper + the dead bench-helper sibling) landed during pre-v1 cleanup and are gone; independent of Phase-4 plugin manifest schema decisions.
Private namespaces. A plugin's writes go to a DID-scoped namespace whose cap is held by the plugin's DID. Manifest shares=none for that namespace blocks delegation; the engine refuses to issue cross-plugin caps for it. Gives plugins a sovereign space (AI agents' working memory, intermediate state) without breaking the cross-plugin sharing model — same machinery, different policy.
Storage-partition seam (Phase-4-Meta-Core G-CORE-1 / #989). WriteContext::namespace_did: Option<Cid> carries the per-DID partition selector through the storage layer. None (the default) routes to the legacy un-namespaced keyspace (byte-identical to the pre-#989 path; no behavioural change for any pre-Phase-4-Meta-Core caller). Some(did) confines the write to a per-DID partition keyed on Cid::as_bytes() of the DID — every Node body, Edge body, label index entry, edge-source/-target index entry, and post-commit change-event subscriber registration lives inside that partition. The runtime cross-DID non-leak invariant (plan §1.A C1) holds structurally at the key-prefix layer: a Backend::scoped(Y) view returns None / Vec::new() for every CID written under namespace_did = Some(X) for X != Y, end-to-end across point read, label range scan, raw key iterate, and change-subscriber fan-out. The seam is the structural hook the Phase-4-Meta-Core encryption-as-confidentiality substrate (#1301) plugs into; the WriteContext shape including this field is the §1.A.FROZEN item-5 locked canary surface (frozen at G-CORE-9 for v1). Authority-side vs. confidentiality-side isolation (CLAUDE.md baked-in #18): this seam closes the authority half on the cooperating-engine path (the namespace selector is honoured by THIS engine's storage layer); the confidentiality half — per-DID encryption of partition bytes so an untrusted-host or peer-holding-ciphertext cannot read across partitions even with raw redb access — is the #1301 substrate that this seam unblocks.
Compile-time linked into the engine binary. Rare. For custom IVM strategies, alternate transports (post-iroh — shaped relays, Nostr, Tor), alternate persistence backends (post-redb — sled, fjall, cloud-KV), custom signature schemes (post-Ed25519 — X25519, BLS, post-quantum), performance-critical primitives that need raw Rust speed beyond SANDBOX.
Transport abstraction boundary (P2P sync layer). The alternate-transport extension point above (post-iroh — Tor / Nostr-relay / shaped relay) is named by the benten_sync::transport_trait::{Transport, TransportEndpoint, TransportConnection} traits (RATIFIED 2026-05-15, §15.3 #1). These abstract the connection layer the Atrium P2P sync runtime exercises (connect / accept / send-bytes / recv-bytes / status / close), routing the transport's address type through the TransportEndpoint::Addr associated type so no iroh-concrete type (iroh::EndpointAddr) appears in the trait contract. The pre-v1 concrete impl, IrohTransport, delegates to the existing iroh-backed transport::Endpoint / transport::Connection with zero behavioral change; the engine-facing API (engine_sync.rs) intentionally stays on the concrete newtypes pre-v1 (no behavioral regression) — the boundary existing pre-v1 is the load-bearing deliverable so the v1 surface contract names the abstraction rather than the concrete. A post-v1 TorTransport / NostrRelayTransport / ShapedRelayTransport implements the three traits as compile-time engine extensions per the trust model below. Mirrors the Renderer-trait swappability pattern (CLAUDE.md baked-in #17). Compile-fenced against accidental iroh-leak by crates/benten-sync/tests/transport_trait_boundary.rs (an iroh-free in-memory mock impl).
Trust model. "You compiled this into your engine binary." Same trust posture as Benten core itself. No UCAN, no manifest, no read_node_as boundary. An engine extension that wants to violate invariants can — the boundary is cargo and code review, not the type system.
Audience. People building the platform itself, not app users. The two categories are intentionally separate worlds; trust models do not transfer between them in either direction.
The browser wasm32 bundle commits to a thin-client posture: it holds in-RAM cache + IndexedDB snapshot data + manifest-store, but contains no iroh transport, no loro CRDT bytes, no redb durable backend, and no wasmtime SANDBOX runtime. Full peers (native Rust on user-owned hardware) are the sync participants; the browser tab reads against snapshot data and writes via fetch to a full peer. The architectural commitment is defended at three rungs so a regression at any one rung is caught by another:
- Source-side cfg-gating.
crates/benten-sync/src/lib.rsfirescompile_error!fortarget_arch = "wasm32", and Cargo.toml restricts the crate's dependency block to[target.'cfg(not(target_arch = "wasm32"))'.dependencies]. A wasm32 build attempt fails at compile time, not at runtime. Pinned bycrates/benten-sync/tests/wasm32_excluded.rs. - Cargo feature-graph closure. No transitive activation of full-peer-only crates from the browser-bundle root crate, even via shared workspace features. Pinned by
bindings/napi/tests/feature_graph_closure_no_test_helpers_in_production.rs. - Built-bundle symbol-section audit. CI's
wasm-browser.ymlrunswasm-objdump -xagainst the produced.wasmartifact and asserts zero matches for the four forbidden crate prefixes (loro,iroh,redb,wasmtime). A regression that pulls any forbidden crate into the bundle fails CI immediately with the matched-symbol output. Pinned bybindings/napi/tests/wasm_bundle_content.rs.
The companion wasm-checks.yml benten-sync-refuses-wasm32 cell additionally asserts that cargo check --target wasm32-unknown-unknown -p benten-sync fails with the compile_error! macro firing — covering the case where the source-side cfg-gating gets accidentally removed but feature-graph closure happens to still hold.
For the paths this will take next, see HOW-IT-WORKS.md "The path from here." For every error the engine surfaces, see ERROR-CATALOG.md.