Status: Active normative architecture document
Date: February 20, 2026 (rev 4)
Implementation status source: TASKLIST.md
Documentation index: docs/INDEX.md
Universal Business Leverage leverages the best of determinism with the best of the stochasticism of LLMs — both comfortable and at maximum potential, with limits expressed by clear rules.
The machine layer (NRF-1, BLAKE3, RB-VM) is deterministic at the content level: given the same canonical input and the same version of rules, it produces the same bytes and the same chip CID. Receipt CIDs remain event-specific by design (see PF-02). The LLM layer operates above it with full creative latitude — but grounded by the Universal Envelope (@type, @id, @ver, @world) and bounded by policies compiled into bytecode. Neither side is constrained to be the other. Determinism doesn't try to be creative. LLMs don't try to be precise. The system is the interface where both do what they're best at.
PF-01 — Determinism Contract. Given the same canonical input (after UNC-1 normalization + Universal Envelope), the same version of rules (NRF-1 /
@ver), and the same declared configuration, UBL produces exactly the same bytes and the same CID. This is content determinism — it is absolute and tested by KATs. Binary determinism (same source → same executable hash) is NOT guaranteed by default; Rust does not produce reproducible binaries without explicit toolchain controls (--remap-path-prefix, lockedcodegen-units, Nix/srtool). Thebinary_hashfield in receipts is observability for forensic auditing, not a trust anchor. Trust comes from the Ed25519 signature chain viaubl_kms. Binary reproducibility is a future hardening phase (Nix/srtool pipeline).
- All Rust, always. Native Rust solutions only. No shelling out, no FFI unless absolutely unavoidable. The ecosystem is rich enough.
- Research before implementing. Before writing any component, search the web. This industry evolves by the minute. Someone may have solved it better, and their solution may fit the pipeline. The question is always: does this fit in canon JSON → canon bytecode → pipeline → gate? If yes, adopt it. If no, build it.
- Everything through the pipeline. No side channels. Every action is a chip, every chip goes through KNOCK→WA→CHECK→TR→WF, every output is a receipt. If it can't be expressed as a chip flowing through the gate, it doesn't belong in the system.
- The gate is the only entry point. Nothing bypasses
ubl_gate. Not admin tools, not debug endpoints, not migrations. If it mutates state, it's a chip. - Auth is the pipeline. There is no separate auth system. Registration =
ubl/userchip. Login =ubl/tokenchip. Permission = policy evaluation at CHECK. Blocking or permitting people is exactly what the pipeline does — it's just a policy on a chip type. - One pipeline, many services. Attestation, witnessing, notarization, proofing, documentation — all are the same pipeline with different
@types and different policies. One copy of the gate served over HTTPS handles all of them. The commercial surface is configuration, not code. - The LLM is an Accountable Advisor. The LLM judges, sorts, suggests, routes, narrates — but the pipeline decides. The LLM signs its work via
ubl/advisoryreceipts. Judged wrong? That LLM. Write it down, move on. It has rights (advise, read context, suggest) and duties (sign, be traceable, be accountable). The first app is AI Passport (ubl/ai.passport) — an LLM's identity, rights, and duties as a chip. - Leverage = Pipeline × Engine.
UBL = Deterministic Pipeline × LLM Engine. Determinism provides proof, enforcement, verification. The LLM Engine provides understanding, advice, judgment. Neither alone is sufficient. The product is greater than the sum. - Software as Story. The receipt chain is a narrative. Each receipt is a sentence, the chain is a paragraph, the chip's lifecycle is a chapter. Design principle: if you can tell the story, you can build the chip. If you can't tell the story, you don't understand the feature yet.
- Ghost is an open question. Three interpretations exist: (a) any DENY at any stage, (b) allowed in but failed during execution, (c) something else. The architecture doesn't depend on settling this now. Every stage receipts. Every receipt is evidence. The
ghostflag is metadata whose precise semantics will be refined as we build.
UBL is not a product. It is a protocol stack — eight layers that turn any domain into a deterministic, auditable, LLM-augmented system.
| Layer | What | Examples |
|---|---|---|
| Chips | The atomic unit — every action, every fact, every intent | ubl/user, ubl/payment, vcx/manifest, ubl/advisory |
| Pipeline | The deterministic engine that processes chips | KNOCK→WA→CHECK→TR→WF, fuel metering |
| Policy Gates | Governance — what's allowed, who decides | Genesis, P0→P1, quorum, dependency chains, autonomia matrix |
| Runtime | The Certified Runtime — executor, arbiter, notary | Deterministic execution, sandbox, fuel, runtime_hash, self-attestation |
| Receipts | Proof of everything that happened | UnifiedReceipt, stage evolution, HMAC-BLAKE3 auth chain, policy trace |
| Registry | Identity + state + history | CID/DID, ChipStore, append-only ledger, Rich URLs |
| Protocols | Domain-specific chip vocabularies | Auth, Money, Media (VCX), Advisory, Documents |
| Products | Configuration on top of protocols | AI Passport, Notarization, Video editor, Payment gateway |
Chips are the input and the output. A receipt is a chip. A policy is a chip. An advisory is a chip. A payment is a chip. A video manifest is a chip. It's chips all the way down.
You never write a new system. You write a new @type, a new policy, and maybe a new WASM adapter. The pipeline, the gate, the receipts, the registry — they're already there. That's the leverage.
- You send a Chip — your intent, your action, your fact.
- The system validates it — is it well-formed? who are you? what are the rules?
- The system executes it — deterministically, under policy, with fuel limits.
- You get a Receipt — signed proof of what happened, why, under which rules.
- It's in the Registry — permanent, verifiable, auditable, with a URL you can share.
Five steps. No acronyms. Works for auth, payments, video, documents, AI advisories — any domain.
Strategic vision, long-horizon protocol narrative, and roadmap content now live in:
docs/visao/MANIFESTO_DA_REINVENCAO.mddocs/visao/VCX-Core.md
This architecture document stays implementation-oriented and normative.
This system descends from the UBL Master Blueprint v2.0 (Chip-as-Code & Registry-First). The Blueprint established four invariant laws (Canon, Determinism, Identity/Scope, Receipt-is-State), the Chip-as-Code model, the fractal RB→Circuit→Policy hierarchy, and the WA→TR→WF pipeline.
The Rust codebase implemented these ideas but evolved significantly from the original spec:
| Blueprint Concept | What the Code Actually Does | Status |
|---|---|---|
| BLAKE3 everywhere | Canon/CID and receipt paths use BLAKE3 consistently across runtime crates. | ✅ Resolved |
| 5 stages: KNOCK→WA→TR→EXECUTE→WF | 5 stages: KNOCK→WA→CHECK→TR→WF. KNOCK is explicit (knock.rs, 11 tests). CHECK is the policy stage. |
✅ Resolved |
| RB-VM opcodes | 19 TLV opcodes: JSON-oriented, linear, receipt-native. No JMP. | ✅ Locked — deliberate redesign |
| Policy as bytecode chip | PolicyBit/Circuit/ReasoningBit in Rust structs with Expression DSL. Fractal policy wiring with per-RB vote traces. | ✅ Working — bytecode compilation deferred |
| S3 key layout | FsCas with hash-sharded paths; NdjsonLedger for filesystem audit log |
✅ Ledger working — S3 backend future |
| Double-Read (cache + canonical path) | Single path only — no caching layer | Future optimization |
| Newtype pattern (Cid, ChipBody, UserId) | ubl_types newtypes (Cid, Did, Kid, World, etc.) are integrated in critical runtime and receipt paths. |
✅ Resolved |
| Parse, Don't Validate | Core pipeline paths parse and anchor once, then execute via typed context. | ✅ Implemented on critical paths |
| Structured logging (tracing) | Runtime + gate use structured tracing spans for operational visibility. |
✅ Resolved (PS4) |
| LLM Advisory at KNOCK (Gate σ) | LLM Observer consumes events post-pipeline, never at KNOCK | ✅ Correct — advisory stays off critical path |
| Receipt as nested JSON | UnifiedReceipt evolves through stages, CID recomputed per append, HMAC-BLAKE3 auth chain |
✅ Resolved S3.1 — 11 unit + 4 integration tests |
| Proptest for canon | Property-based suites are active in core canon/numeric/vm paths. | ✅ Resolved |
The four Laws remain inviolable. Everything else is implementation detail that evolved.
| Crate | Role | Status |
|---|---|---|
ubl_ai_nrf1 |
NRF-1.1 canonical encoding, CID (BLAKE3), Universal Envelope, chip format | ✅ Working (108 tests) |
rb_vm |
Deterministic stack VM, TLV bytecode, fuel metering | ✅ Working (79 tests) |
ubl_runtime |
Full pipeline: KNOCK→WA→CHECK→TR→WF, auth, onboarding, genesis, advisory, event bus | ✅ Working (352 tests) |
ubl_receipt |
UnifiedReceipt with stage evolution, HMAC-BLAKE3 auth chain, Decision enum | ✅ Working (22 tests) |
ubl_chipstore |
CAS storage, InMemory + Sled backends, indexing, query builder | ✅ Wired into pipeline WF stage |
ubl_ledger |
NdjsonLedger (filesystem), InMemoryLedger (testing) |
✅ Working (6 tests) |
ubl_did |
DID document generation, did:cid: resolution |
✅ Minimal but functional |
ubl_config |
BASE_URL from env |
✅ Trivial |
ubl_cli |
ublx verify / ublx build for .chip files |
✅ Working |
ubl_gate |
Axum HTTP gateway — POST /v1/chips, GET /v1/chips/:cid, GET /v1/receipts/:cid/trace, advisory endpoints |
✅ Fully rewritten with real ChipStore |
logline |
Structured text parser/serializer (from TDLN) — Layer 2 renderer | ✅ Working (full roundtrip, tokenizer, AST, builder) |
| Module | Role |
|---|---|
pipeline/mod.rs + pipeline/processing.rs + pipeline/stages/* |
Modular KNOCK→WA→CHECK→TR→WF orchestration, real rb_vm execution at TR |
auth.rs |
8 onboarding chip types (ubl/app, ubl/user, ubl/tenant, ubl/membership, ubl/token, ubl/revoke, ubl/worldscope, ubl/role), body validation, dependency chain enforcement (34 unit + 10 integration tests) |
genesis.rs |
Bootstrap genesis chip in ChipStore at startup, idempotent, self-signed |
knock.rs |
Input validation: size ≤1MB, depth ≤32, array ≤10K, no dup keys, valid UTF-8, required @type/@world (11 tests) |
error_response.rs |
Canonical UblError with Universal Envelope format, stable error codes, and HTTP/JSON-RPC mappings |
advisory.rs |
Advisory engine for post-CHECK and post-WF LLM hooks |
ai_passport.rs |
AI Passport chip type — LLM identity, rights, duties |
ledger.rs |
LedgerWriter trait, NdjsonLedger, InMemoryLedger (6 tests) |
event_bus.rs |
In-process event bus for receipt events |
durable_store.rs |
SQLite durability boundary: atomic commit (receipts + idempotency + outbox) |
outbox_dispatcher.rs |
Durable outbox claim/ack/nack + retry/backoff worker |
transition_registry.rs |
Deterministic TR bytecode resolution by @type/profile/override |
POST /v1/chips → KNOCK (validate) → WA (seal intent) → CHECK (policy + onboarding) → TR (rb_vm) → WF (receipt + store)
Working: Full 5-stage pipeline with real rb_vm execution. Chips stored in ChipStore. UnifiedReceipt evolves through stages. Genesis bootstrap at startup. Onboarding dependency chain enforced (app→user→tenant→membership→token→revoke). Canonical error responses. Advisory engine. AI Passport. Event bus. Gate serves real ChipStore lookups and receipt traces.
Hardening in progress: reproducible-build hardening remains. Runtime self-attestation, structured tracing, and durability boundary are implemented.
| Decision | Value | Rationale |
|---|---|---|
| Hash | BLAKE3, 32 bytes | Fast, parallel, no length-extension. rb_vm already uses it. |
| CID format | b3: + lowercase hex, 64 chars |
b3:a1b2c3... (32 bytes = 64 hex chars) |
| Strings | NFC normalized, BOM rejected | Already enforced in ubl_ai_nrf1::nrf.rs |
| Prohibited chars | \u0000–\u001F in source YAML |
Escape required in NRF string encoding |
| Surrogates | Reject unpaired surrogates | Invalid UTF-8 → DENY at KNOCK |
| Numbers | i64 for simple integers; UNC-1 @num objects for all other numerics |
json_to_nrf rejects raw floats. See §3.3 UNC-1. |
| Decimals | UNC-1 dec/1 (mantissa × 10^−scale, bigint strings) |
Replaces planned NrfValue::Decimal(i128, u8). See docs/canon/UNC-1.md. |
| Null vs absence | Null values REMOVED from maps | Absence ≠ null; {"a": null} canonicalizes to {} |
| Map key order | Strict Unicode code point ascending, post-NFC | Already uses BTreeMap in nrf.rs |
| Duplicate keys | Reject (DENY) | Must fail at parse, not silently deduplicate |
All non-integer numbers use UNC-1 (@num tagged objects). No IEEE-754 in canon.
| Kind | Tag | Fields | Use Case |
|---|---|---|---|
| INT | int/1 |
v (string bigint) |
Counts, IDs |
| DEC | dec/1 |
m (mantissa), s (scale) |
Finance, measurement |
| RAT | rat/1 |
p, q (strings) |
Exact fractions |
| BND | bnd/1 |
lo, hi (Num) |
IEEE-754 imports, uncertainty |
- Optional
ufield for units ("USD","kg", etc.) - Rounding mode is explicit at the operation site, never in the data
f64imports becomebnd/1(minimal interval) — imprecision is always visible- NRF-1 encodes UNC-1 atoms as MAP — zero changes to encoding layer
- Full spec: docs/canon/UNC-1.md
- Crate:
crates/ubl_unc1/ - Schema:
schemas/unc-1.schema.json - KATs:
kats/unc1/unc1_kats.v1.json
Every chip exists in two canonical forms. Both are deterministic; one is for machines, the other is for LLMs and humans.
Layer 0 — Machine Canon (NRF-1 bytecode)
The deterministic binary encoding. This is what gets hashed → CID, signed, and stored. One byte per type tag. No ambiguity. The CID is derived exclusively from this layer.
Layer 1 — LLM Canon (Anchored JSON)
A minimal, flat JSON derived deterministically from the bytecode. Designed for LLM consumption without requiring computation. Mandatory anchor fields prevent drift:
{"@id":"b3:a1b2...","@type":"ubl/user","@ver":"1.0","@world":"a/acme/t/prod","body":{"email":"bob@acme.com","theme":"dark"}}Rules:
- Flat: Minimal nesting (max 2 levels in body). No deep trees.
- Anchored:
@id(CID),@type,@ver,@world(app/tenant scope) are always present at the top. These ground the LLM — it always knows what it's reading, which version, and where it lives. - Low overhead: No pretty-printing, no comments, no trailing commas. One line per chip.
- Deterministic:
LLM_Canon(chip) = json_from_nrf1(nrf1_bytes). Same bytecode → same JSON → always. - Read-only contract: The LLM reads this form. To write, it produces this form, which gets compiled to NRF-1 bytecode and verified.
The three-layer canonical stack:
Layer 0: NRF-1 bytecode → Machine (hash, sign, store)
Layer 1: Anchored JSON → LLM (read, write, reason)
Layer 2: LogLine → Human (debug, audit, observe) — future
All three are deterministic derivations of the same data. Layer 0 is truth. Layer 1 is derived. Layer 2 is rendered. The logline crate (from TDLN, already built) is the renderer for Layer 2 — but human-facing representation is ultimately UI, not text. Layer 2 is deferred.
Key rule for Layer 1: @type is always the first key, @id always second. LLMs read left-to-right; first token = grounding.
Universal Envelope Rule: The anchored JSON is not just for chips — it is the base format for everything in the system. Chips, receipts, events, API responses, error payloads, policy traces — all share the same minimum fields:
{"@id":"...","@type":"...","@ver":"...","@world":"..."}Different types add fields on top (body, stages, decision, error, trace, etc.) but no message may have fewer than these four anchors. This means:
- A receipt is
{"@id":"b3:...","@type":"ubl/wf","@ver":"1.0","@world":"a/acme/t/prod","stages":[...],"decision":"allow",...} - An event is
{"@id":"b3:...","@type":"ubl/event","@ver":"1.0","@world":"a/acme/t/prod","event_type":"receipt.created",...} - An error is
{"@id":"b3:...","@type":"ubl/error","@ver":"1.0","@world":"a/acme/t/prod","code":"POLICY_DENIED",...} - A policy is
{"@id":"b3:...","@type":"ubl/policy","@ver":"1.0","@world":"a/acme/t/prod","rules":[...],...}
One format. Always anchored. Always parseable by the same code. An LLM reading any UBL artifact always sees the same four fields first and immediately knows what it is, what version, and where it belongs.
Current state: ubl_ai_nrf1::compute_cid and rb_vm both use BLAKE3. CID is derived from NRF bytes and encoded as b3:<hex>.
Contract: one hash function everywhere in the trust path.
cid = "b3:" + hex::encode(blake3::hash(nrf1_bytes).as_bytes())
| Code | Type | Stage |
|---|---|---|
0x10 |
Chip (generic) | — |
0x11 |
WA Receipt | Stage 1 |
0x12 |
TR Receipt | Stage 3 |
0x13 |
WF Receipt | Stage 4 |
0x14 |
Policy | — |
0x15 |
Advisory | — |
0x16 |
Knock | Stage 0 |
0x17 |
Ghost | WBE |
0x18 |
Unified Receipt | Future |
Flags byte (reserved): bit 0 = ghost, bit 1 = signed, bits 2-7 = reserved.
rb_vm is the most mature crate. It implements a deterministic stack VM with:
- 19 opcodes in TLV (Type-Length-Value) bytecode format
- Fuel metering — 1 unit per opcode, configurable limit
- No-IO by construction — only
CasProviderandSignProvidertraits - Ghost mode — same execution, flagged in receipt
- 10 Laws verified by 633 lines of tests with golden CIDs
| Byte | Opcode | Fuel | Stack Effect | Payload |
|---|---|---|---|---|
0x01 |
ConstI64 |
1 | → i64 | 8 bytes BE |
0x02 |
ConstBytes |
1 | → bytes | N bytes |
0x03 |
JsonNormalize |
1 | bytes → json | — |
0x04 |
JsonValidate |
1 | json → json | — |
0x05 |
AddI64 |
1 | i64, i64 → i64 | — |
0x06 |
SubI64 |
1 | i64, i64 → i64 | — |
0x07 |
MulI64 |
1 | i64, i64 → i64 | — |
0x08 |
CmpI64 |
1 | i64, i64 → bool | 1 byte op |
0x09 |
AssertTrue |
1 | bool → ∅ | — |
0x0A |
HashBlake3 |
1 | bytes → bytes | — |
0x0B |
CasPut |
1 | bytes → cid | — |
0x0C |
CasGet |
1 | cid → bytes | — |
0x0D |
SetRcBody |
1 | json → ∅ | — |
0x0E |
AttachProof |
1 | cid → ∅ | — |
0x0F |
SignDefault |
1 | (no-op, signing at EmitRc) | — |
0x10 |
EmitRc |
1 | → (terminates) | — |
0x11 |
Drop |
1 | a → ∅ | — |
0x12 |
PushInput |
1 | → cid | 2 bytes BE index |
0x13 |
JsonGetKey |
1 | json → i64 | UTF-8 key |
| Decision | Value | Rationale |
|---|---|---|
| Fuel ceiling per TR | 1,000,000 units | Prevents runaway; DENY if exceeded |
| Cost model | 1 unit/opcode (MVP) | Future: weighted by opcode class |
| Types | i64, bool, bytes, cid, json, unit |
No implicit conversions — type mismatch = DENY |
| Halting | No JMP/LOOP opcodes | Fuel-bounded linear execution only |
| Signature domain | "ubl-rb-vm/v1" context string |
Prevents cross-domain replay |
| Arithmetic overflow | Saturating (saturating_add/sub/mul) |
Already implemented |
| Byte | Opcode | Status |
|---|---|---|
0x14 |
Dup |
✅ Implemented S2.6 |
0x15 |
Swap |
✅ Implemented S2.6 |
0x16 |
VerifySig |
✅ Implemented S2.6 — Ed25519 verify with domain separation |
0x17 |
DecimalAdd |
Planned — depends on NrfValue::Decimal |
0x18 |
DecimalCmp |
Planned — depends on NrfValue::Decimal |
KNOCK → WA (ghost) → CHECK (policy) → TR (rb_vm) → WF (final receipt)
│ │ │ │ │
│ │ │ │ └─ Store in ChipStore
│ │ │ └─ Execute bytecode, emit RC
│ │ └─ Evaluate policy chain (genesis→app→tenant→chip)
│ └─ Create ghost record, freeze time, assign policy_cid
└─ Validate input size/depth, rate limit, assign nonce
Single UnifiedReceipt that evolves through stages. CID recomputed after each stage append. HMAC-BLAKE3 auth chain links stages. Its JSON form follows the Universal Envelope — the receipt is just another chip that an LLM can read without special-casing. 11 unit tests + 4 integration tests.
Gate read paths (GET /v1/receipts/:cid, GET /v1/receipts/:cid/trace, GET /v1/chips/:cid/verify) verify the receipt auth-chain before returning success. Broken chains return TAMPER_DETECTED (HTTP 422).
struct UnifiedReceipt {
v: u32, // Schema version
t: String, // RFC-3339 UTC
did: String, // Issuer DID
subject: Option<String>, // Subject DID
kid: String, // Key ID: did:key:z...#ed25519
nonce: String, // Anti-replay (see §6.2)
stages: Vec<StageExecution>, // Append-only
decision: Decision, // Current decision state
effects: serde_json::Value, // Side-effects record
rt: RuntimeInfo, // binary_sha256, env, certs
prev_receipt_cid: Option<String>, // Chain linkage
receipt_cid: String, // b3:hash(NRF(self_without_sig))
sig: String, // Ed25519 JWS detached
}
struct StageExecution {
stage: PipelineStage, // WA, CHECK, TR, WF
timestamp: String, // RFC-3339 UTC
input_cid: String, // What entered this stage
output_cid: Option<String>, // What this stage produced
fuel_used: Option<u64>, // For TR stage
policy_trace: Vec<PolicyTraceEntry>, // For CHECK stage
signature: String, // Stage executor signature
auth_token: String, // HMAC proving stage N authorizes stage N+1
}Auth chain: Each stage computes auth_token = HMAC-BLAKE3(stage_secret, prev_stage_cid || stage_name). Next stage verifies before executing.
CID evolution: receipt_cid recomputed after each stage append. The WF receipt_cid is the final canonical CID.
TR stage creates a Vm instance and executes TLV bytecode selected by TransitionRegistry (not a fixed passthrough blob). PipelineCas, PipelineSigner, and PipelineCanon implement rb_vm traits. Fuel usage, bytecode provenance, and adapter metadata are recorded in TR vm_state.
Resolution order:
- chip override
@tr.bytecode_hex - chip override
@tr.profile - env map
UBL_TR_BYTECODE_MAP_JSON - env map
UBL_TR_PROFILE_MAP_JSON - built-in default profile by
@type
let resolution = transition_registry.resolve(&request.chip_type, &request.body)?;
let instructions = tlv::decode_stream(&resolution.bytecode)?;
let outcome = vm.run(&instructions)?;
// outcome.rc_cid, outcome.fuel_used, outcome.steps + resolution metadata -> TR receipt| Check | Limit | Action |
|---|---|---|
| Max chip size | 1 MB | DENY at KNOCK |
| Max receipt size | 1 MB | DENY at WF |
| Max JSON depth | 32 levels | DENY at KNOCK |
| Max array length | 10,000 elements | DENY at KNOCK |
| Duplicate keys | 0 allowed | DENY at KNOCK |
| Input normalization (ρ) | NFD→NFC, BOM strip, map null-strip, timestamp/set normalization | Normalize at KNOCK; reject collisions/control chars |
| Cost per byte | 1 fuel unit per 1KB | Added to TR fuel budget |
ρ validation/normalization failures include a JSON path (for example body.name or body.profile.email) to make rejection causes actionable.
Genesis Policy (immutable, self-signed)
└─ App Policy (per application)
└─ Tenant Policy (per tenant within app)
└─ Chip Policy (per chip type)
Evaluation order: genesis first (most general), chip-specific last. First DENY short-circuits.
policy_cidis immutable once written into a WA receipt- Policy migration: deploy new policy chip → update app/tenant config to reference new
policy_cid→ new chips use new policy; old receipts remain valid under old policy - No retroactive policy changes — a receipt's
policy_cidis its law forever
Current: Policies resolved at runtime via PolicyLoader.load_policy_chain().
Target: Compile-time resolution with lockfile:
# policy.lock
genesis: b3:abc123...
app/acme: b3:def456...
tenant/acme-prod: b3:789abc...TR stage verifies lockfile CIDs match loaded policies. Divergence = DENY.
The policy model is fractal — the same pattern at every scale:
Layer 0: Reasoning Bit (RB) — atomic decision: ALLOW/DENY/REQUIRE
Layer 1: Circuit — graph of RBs wired together
Layer 2: PolicyBit — composition of Circuits into governance
Layer 3+: PolicyBits compose further (fractal)
A Reasoning Bit is a transistor. A Circuit is an integrated circuit. A PolicyBit is a board. Boards compose into systems. Same pattern, every level.
- ReasoningBit: Atomic decision unit with
Expressioncondition language. Evaluates to Allow/Deny/Require. Every RB produces a receipt proving its decision. - Circuit: Composes RBs with
CompositionMode(Sequential/Parallel/Conditional) andAggregationMode(All/Any/Majority/KofN/FirstDecisive). A Circuit produces a composed receipt. - PolicyBit: Groups circuits with a
PolicyScope(chip types, operations, level). The PolicyBit produces the final governance receipt.
K-of-N: The policy trace must expose individual RB votes. SEAL markers identify which RBs are audit anchors.
The genesis chip is the root PolicyBit — the first board in the system. Every other policy inherits from it.
Every chip lives in a world. The @world field in the Universal Envelope is the logical address:
@world = "a/{app}/t/{tenant}"
a/acme/t/prod— the production tenant of the Acme appa/acme/t/dev— the dev tenant of the same appa/lab512/t/dev— LAB 512's dev environment
Rules:
- A chip cannot reference chips in a different
@worldunless the policy explicitly allows cross-world reads. - The gate resolves
@worldfrom the authenticated DID's membership. No world in the request = DENY at KNOCK. @worldis frozen into the WA receipt and cannot change after that point.- The genesis policy lives at
@world = "a/_system/t/_genesis"— the root world.
| Decision | Value |
|---|---|
| DID method | did:key:z... Ed25519 with strict multicodec (0xED01) support + compat fallback |
| Key ID format | did:key:z...#ed25519 |
| Key rotation | New kid published as ubl/key.rotate chip; old kid valid for verification of past receipts |
UBL_STAGE_SECRET fallback is derived from signing key material using domain-separated BLAKE3 (ubl.stage_secret.v1), never by reusing raw Ed25519 private key bytes.
| Signing curve | Ed25519 (RFC 8032) |
Each WA receipt includes a nonce field (16-byte random hex). Pipeline checks against seen_nonces set.
nonce = BLAKE3(did || tenant_id || monotonic_counter)
- Counter is per-key, per-tenant, monotonically increasing
- Gate rejects WA with nonce ≤ last-seen nonce for that (did, tenant) pair
- Anti-replay window: 5 minutes for clock skew tolerance
All signatures include a context prefix to prevent cross-domain replay:
| Context | Domain String |
|---|---|
| Receipt signing | "ubl/receipt/v1" |
| RB-VM signing | "ubl-rb-vm/v1" |
| Policy signing | "ubl-policy/v1" |
| URL signing | "ubl/rich-url/v1" |
Format: sig = Ed25519.sign(key, domain_string || BLAKE3(payload))
ubl_chipstore provides:
ChipStoreBackendtrait withInMemoryBackendandSledBackendChipIndexerwith in-memory indexes (type, tag, executor) rebuilt via backendscan_all()ChipQueryBuilderwith sorting, pagination, filteringCommonQueriesfor customers, payments, audit trails
Integration: UblPipeline accepts Arc<ChipStore> and calls store_executed_chip() in the WF stage. Wired since S3.3.
{root}/{prefix[0:2]}/{prefix[2:4]}/{full_cid}
Example: cas/a1/b2/b3:a1b2c3d4e5f6...
- GET is O(1) by CID
- Idempotent writes (content-addressed)
FsCasinrb_vmalready implements this with BLAKE3
ubl_ledger provides LedgerWriter trait with NdjsonLedger (filesystem) and InMemoryLedger (testing). 6 tests.
- Append-only NDJSON audit log per (app, tenant)
- Receipt and ghost lifecycle events
- Failures warn-logged, never block pipeline
- Future: S3-compatible object storage (Garage/MinIO for self-hosted)
WASM adapters run in the TR stage for chips that require external effects (email, payment, etc.).
| Constraint | Value |
|---|---|
| No filesystem | WASI FS disabled |
| No clock | clock_time_get returns frozen WA timestamp |
| No network | All I/O via injected CAS artifacts |
| Memory limit | 64 MB per execution |
| Fuel limit | Shared with RB-VM fuel budget |
| Module pinning | sha256(wasm_module) recorded in receipt rt field |
Input: NRF-1 bytes (chip body + context)
Output: NRF-1 bytes (result + effects)
The adapter receives a single NRF-1 encoded input and must return a single NRF-1 encoded output. No other I/O.
Each adapter is a chip of type ubl/adapter:
ubl_chip: "1.0"
metadata:
type: "ubl/adapter"
id: "email-sendgrid-v1"
body:
wasm_cid: "b3:..." # CID of the WASM module
wasm_sha256: "..." # SHA-256 of the WASM binary
abi_version: "1.0"
fuel_budget: 100000
capabilities: ["email.send"]{
"schema_version": "1.0",
"event_type": "ubl.receipt.created",
"receipt_cid": "b3:...",
"receipt_type": "ubl/wf",
"decision": "allow",
"duration_ms": 42,
"timestamp": "2026-02-15T12:00:00Z",
"pipeline_stage": "wf",
"idempotency_key": "b3:...",
"metadata": { ... }
}- Idempotency key =
receipt_cid(exactly-once by CID) - Schema version field for forward compatibility
- Topic:
ubl.receiptson Iggy message broker
Every WF receipt must include:
fuel_used: Total fuel consumed in TRrb_count: Number of reasoning bits evaluatedartifact_cids: List of CIDs produced during executionpolicy_trace: Full RB vote breakdown
Consumes events from Iggy, performs mock AI analysis. Stays outside the critical path — advisory only, never blocks pipeline.
The LLM operates beside the pipeline, not inside it. It is an Accountable Advisor — it acts in the world and signs what it did.
| Stage | LLM Role | Binding? |
|---|---|---|
| Pre-KNOCK | Semantic triage: "does this look like what the user intended?" | No — advisory only |
| Post-CHECK | Explain denial: "policy X rejected because..." | No — narration |
| Post-TR | Summarize execution: "this chip did X, consumed Y fuel" | No — narration |
| Post-WF | Route/classify: "this receipt belongs in category Z" | No — suggestion |
| On-demand | Audit storytelling: "here's what happened in this receipt chain" | No — narration |
The LLM never overrides an RB decision. It never produces a CID. It never touches the receipt chain directly.
Every LLM action produces a ubl/advisory chip — signed by the LLM's AI Passport key, following the Universal Envelope:
{"@type":"ubl/advisory","@id":"b3:...","@ver":"1.0","@world":"a/acme/t/prod","passport_cid":"b3:...","action":"classify","input_cid":"b3:...","output":{"category":"compliance","confidence":0.92},"model":"gpt-4","seed":0}Advisory receipts are stored, indexed, and auditable — but never block the pipeline.
The first app. An LLM's identity, rights, and duties as a chip:
{"@type":"ubl/ai.passport","@id":"b3:...","@ver":"1.0","@world":"a/acme/t/prod","model":"gpt-4","provider":"openai","rights":["advise","classify","narrate"],"duties":["sign","trace","account"],"scope":["a/acme/*"],"fuel_limit":100000,"signing_key":"did:key:z..."}The passport enters the registry through the same door as everything else — POST /v1/chips.
{
"error": true,
"code": "POLICY_DENIED",
"message": "Genesis policy: chip body exceeds 1MB limit",
"receipt_cid": "b3:...",
"link": "/v1/receipts/b3:.../trace",
"details": {
"policy_id": "genesis",
"rb_id": "size_limit",
"limit": 1048576,
"actual": 2097152
}
}| Code | Meaning | Stage |
|---|---|---|
INVALID_INPUT |
Malformed JSON, size exceeded, depth exceeded | KNOCK |
CANON_ERROR |
NRF-1 encoding failure, BOM, invalid Unicode | KNOCK/WA |
POLICY_DENIED |
Policy evaluation returned DENY | CHECK |
FUEL_EXHAUSTED |
TR execution exceeded fuel limit | TR |
TYPE_MISMATCH |
RB-VM type error | TR |
STACK_UNDERFLOW |
RB-VM stack underflow | TR |
CAS_NOT_FOUND |
CasGet on missing CID | TR |
SIGN_ERROR |
Signature generation/verification failure | WF |
STORAGE_ERROR |
ChipStore/Ledger write failure | WF |
invalid_signature |
Rich URL / receipt signature invalid (strict mode) | Verify |
runtime_hash_mismatch |
Rich URL rt differs from receipt runtime hash |
Verify |
idempotency_conflict |
Replay key already committed in durable idempotency store | WF |
durable_commit_failed |
Atomic SQLite commit failed (receipts + idempotency + outbox) |
WF |
INTERNAL_ERROR |
Unexpected system error | Any |
REPLAY_DETECTED |
Nonce reuse detected | WA |
Every error that reaches WF produces a DENY receipt with full policy_trace. Errors before WA (KNOCK failures) return HTTP 400 without a receipt.
https://{host}/{app}/{tenant}/receipts/{receipt_id}.json
#cid={receipt_cid}
&did={issuer_did}
&rt={binary_sha256}
&sig={url_signature}
A rich URL contains enough information to:
- Fetch the receipt JSON from the URL path
- Recompute
b3:hash(NRF(receipt_body))and verify it matchescid - Verify
sigagainstdid:keypublic key with domain"ubl/rich-url/v1" - Verify
rtmatches the expected runtime binary hash
Rollout mode is controlled by UBL_RICHURL_VERIFY_MODE:
shadow: verify and log, do not fail requeststrict: fail-closed on any verification mismatch
For offline use, the chip data can be embedded:
ubl://{base64url(compressed_chip)}?cid={cid}&sig={sig}
Max URL length: 2KB (QR code limit). Larger chips use the hosted URL format.
| Resource | Limit |
|---|---|
| Chip body (WA input) | 1 MB |
| Receipt (WF output) | 1 MB |
| URL (self-contained) | 2 KB |
| JSON depth | 32 levels |
| Array length | 10,000 elements |
| Map keys | 1,000 per object |
| String length | 1 MB |
- Per-DID: 100 chips/minute
- Per-tenant: 1,000 chips/minute
- Per-IP (unauthenticated): 10 chips/minute
- Fuel cost per byte: 1 unit per 1KB of input
KNOCK stage rejects early (before WA) on:
- Oversized body
- Excessive nesting depth
- Duplicate JSON keys
- Invalid UTF-8
- Missing required fields (
@type)
Two distinct determinism levels exist in the pipeline:
Chip CID — fully deterministic.
Same canonical content → same NRF-1 bytes → same BLAKE3 hash → same b3: CID.
This holds across machines, runs, and time. Verified by rb_vm golden CID tests.
Receipt CID — contextually unique.
Receipts include frozen_time (WA), nonce (anti-replay), timestamp per stage,
and RuntimeInfo (binary hash, build meta). These fields are intentionally non-reproducible:
the receipt is proof that this specific execution happened at this moment on this binary.
Same chip processed twice → same chip CID, different receipt CIDs.
Consequence: never compare receipt CIDs for content equality. Compare chip CIDs.
Receipt CIDs are identifiers of events, not content. The auth chain
(HMAC-BLAKE3 per stage) proves ordering and integrity within a single execution,
not reproducibility across executions.
Verified by: rb_vm golden CID tests + pipeline integration tests + receipt_cid_is_deterministic test (same inputs including forced timestamp → same CID).
Changing opcode costs = new VM version. Old receipts remain valid under old cost table.
Given only
chips/andreceipts/directories,ublx verifyreconstructs and verifies every receipt bit-for-bit.
A receipt's
policy_cidis its law forever. New policy = new CID = new chips only.
This section is intentionally evidence-based. It records what is implemented and measured; it does not define dated milestones or fixed-duration windows.
Note: sprint-phase test numbers in this section are historical snapshots at delivery time. Use the measured table in this section for current totals.
| Sprint | Goal | Key Deliverables | Tests |
|---|---|---|---|
| S1 — Canon + CID | Lock canonical encoding, Universal Envelope | NRF-1.1 encoding, CID computation, ublx CLI, type code table |
64 (ubl_ai_nrf1) |
| S2 — RB-VM + Policy | Wire rb_vm into pipeline, lock policy resolution | Real TR stage execution, fuel ceiling, unified Decision enum, nonce/anti-replay, policy lockfile |
33 (rb_vm) |
| S3 — Receipts + Storage + Gate | Unified receipt, persistent storage, end-to-end flow | UnifiedReceipt with HMAC chain, ChipStore in pipeline, NdjsonLedger, KNOCK stage, canonical errors, gate rewrite, genesis bootstrap |
22 (receipt) + 290 (runtime) |
| S4 — WASM + URLs + EventBus | External effects, observability, portable URLs | WASM adapter ABI, adapter registry, Rich URL generation, event bus with idempotency, ublx explain |
— |
| Phase | Goal | Key Deliverables | Tests |
|---|---|---|---|
| PS1 — AI Passport | First product on the pipeline | AI Passport chip type, advisory wiring, gate endpoints | — |
| PS2 — Auth as Pipeline | Auth IS the pipeline — no separate auth system | auth.rs with 8 chip types, onboarding dependency chain, validate_onboarding_chip at CHECK, drift endpoints removed |
34 + 10 integration |
| Onboarding | Full lifecycle | ubl/app → ubl/user → ubl/tenant → ubl/membership → ubl/token → ubl/revoke. Dependency chain enforced. DependencyMissing (409) error code. |
Included in 352 total (ubl_runtime) |
| Item | Deliverables | Tests |
|---|---|---|
| H1 Signing key from env | ubl_kms crate, signing_key_from_env(), domain separation |
16 |
| H2 Real DID resolution | All placeholder DIDs replaced, did:key:z... derived from Ed25519 via ubl_kms |
— |
H3 NaiveCanon → full ρ |
RhoCanon in rb_vm/src/canon.rs, NFC, BOM rejection, null stripping, key sorting, idempotent |
19 |
| H4 P0→P1 rollout automation | scripts/rollout_p0_p1_check.sh + make rollout-check preflight (runtime hash allowlist, activation window, quorum, break-glass) |
— |
| H7 Signature domain separation | domain::RECEIPT, RB_VM, CAPSULE, CHIP in ubl_kms |
— |
| H8 Rate limiting | Sliding-window per-key, GateRateLimiter (IP/tenant/DID), prune() |
13 |
| H9 UNC-1 core ops | ubl_unc1 crate: add/sub/mul/div with promotion, to_dec, to_rat, from_f64_bits, BND intervals |
57 |
| H10 Policy lockfile | PolicyLock with YAML parse/serialize, pin(), verify() |
11 |
| H11 RuntimeInfo + BuildMeta | RuntimeInfo::capture(), BLAKE3 binary hash, BuildMeta, wired into every receipt |
7 |
| H13 ρ test vectors | 14 JSON edge-case files in kats/rho_vectors/, 16 integration tests |
16 |
H14 ubl_kms crate |
sign_canonical, verify_canonical, strict+compat DID/KID derivation |
16 |
H15 Prometheus /metrics |
Counters + histogram on gate | — |
| Item | Deliverables | Tests |
|---|---|---|
| PR-A P0.1 Rigid idempotency | IdempotencyStore keyed by (@type,@ver,@world,@id), replay returns cached receipt, wired into process_chip |
10 |
| PR-A P0.2 Canon-aware rate limit | CanonFingerprint (BLAKE3 of NRF-1 bytes) + CanonRateLimiter, cosmetic JSON variations hit same bucket |
7 |
| PR-A P0.3 Secure bootstrap | capability.rs — Capability struct, ubl/app + first ubl/user require cap.registry:init, wired into CHECK |
15 |
| PR-A P0.4 Receipts-as-AuthZ | ubl/membership requires cap.membership:grant, ubl/revoke requires cap.revoke:execute, audience/scope/expiration validation |
— |
| PR-B P1.5 Canonical stage events | ReceiptEvent extended with input_cid, output_cid, binary_hash, build_meta, world, actor, latency_ms |
1 |
| PR-B P1.6 ETag/cache | GET /v1/chips/:cid returns ETag=CID, Cache-Control: immutable, If-None-Match → 304 |
— |
| PR-B P1.7 Unified error taxonomy | 4 new ErrorCode variants (401/404/429/503), category() → 8 categories, mcp_code() → JSON-RPC |
7 |
| PR-C P2.8 Manifest generator | GateManifest → OpenAPI 3.1, MCP tool manifest, WebMCP manifest. Gate serves /openapi.json, /mcp/manifest, /.well-known/webmcp.json |
14 |
| PR-C P2.9 MCP server proxy | POST /mcp/rpc — JSON-RPC 2.0 with tools/list + tools/call dispatching ubl.deliver, ubl.query, ubl.verify, registry.listTypes |
— |
| PR-C P2.10 Meta-chips | ubl/meta.register (mandatory KATs, reserved prefix check), ubl/meta.describe, ubl/meta.deprecate |
16 |
Method: cargo test -p <crate> -- --list (unit + integration test harness totals)
| Crate | Tests |
|---|---|
rb_vm |
79 |
ubl_receipt |
22 |
ubl_runtime |
352 |
ubl_ai_nrf1 |
108 |
ubl_kms |
16 |
ubl_unc1 |
57 |
ubl_chipstore |
10 |
ubl_types |
24 |
ubl_gate |
21 |
| Total (measured set) | 689 |
Current hardening baseline is closed for critical paths. Incremental type-safety expansions can continue opportunistically as refactoring work, not as a release gate.
Future-facing protocol horizons were moved out of this normative architecture file and consolidated in:
docs/visao/MANIFESTO_DA_REINVENCAO.mddocs/visao/VCX-Core.md
| Item | Location | Severity | Status |
|---|---|---|---|
ubl_ai_nrf1::compute_cid |
✅ Fixed — BLAKE3 unified across runtime and VM | ||
Decision enums |
ubl_runtime vs ubl_receipt |
✅ Fixed S2.2 — unified to ubl_receipt::Decision |
|
pipeline.rs |
✅ Fixed S2.1 — real rb_vm execution | ||
ubl_receipt::SIGNING_KEY |
✅ Fixed H1/H14 — ubl_kms, signing_key_from_env() |
||
ubl_ledger is all no-ops |
ubl_ledger::lib.rs |
✅ Fixed S3.4 — NdjsonLedger + InMemoryLedger |
|
UblPipeline |
✅ Fixed S3.3 — Arc<ChipStore> persists at WF |
||
| ✅ Fixed S2.3 — 16-byte hex nonce + PR-A P0.1 rigid idempotency | |||
"did:key:placeholder" in WA stage |
✅ Fixed H2 — real did:key:z... from Ed25519 via ubl_kms |
||
NaiveCanon in rb_vm |
✅ Fixed H3 — RhoCanon with full ρ rules (19 tests) |
||
50 in WF stage |
✅ Fixed S3.7 — real Instant::now() timing |
||
ubl_receipt |
✅ Fixed S3.1 — UnifiedReceipt with HMAC chain |
||
pipeline.rs |
✅ Fixed S3.5 — explicit knock.rs (11 tests) |
||
ubl_gate |
✅ Fixed S3.3 — real ChipStore lookups | ||
✅ Fixed S3.6 + PR-B P1.7 — UblError with 8-category taxonomy |
|||
ubl_ai_nrf1::chip_format |
✅ Fixed C2 — tests were already passing | ||
ubl_runtime |
✅ Fixed H11 — RuntimeInfo::capture(), BLAKE3 binary hash, BuildMeta |
||
eprintln! only |
✅ Fixed F2 — tracing spans and structured logs wired | ||
✅ Fixed H5 — ubl_types newtypes integrated |
|||
| Parse, Don't Validate (beyond critical paths) | Pipeline + chip types | 🟢 Minor | ✅ Core paths migrated; incremental expansion remains optional hardening |
This document is the engineering source of truth. Code that contradicts it is a bug. Decisions marked LOCKED require a new document version to change.
UBL is a protocol stack, not a product pitch. Eight layers — Chips, Pipeline, Policy Gates, Runtime, Receipts, Registry, Protocols, Products — that turn any domain into a deterministic, auditable, LLM-augmented system. You never write a new system. You write a new @type, a new policy, and the leverage is already there.