Witbitz docs HomeTrustAll docs

The verified client, in full

Everything a browser runs for a Space is delivered from an origin, and an origin can be compromised — a popped hosting account, a bad deploy, a poisoned cache. The verified client closes that gap on two orthogonal planes, and a serious client needs both:

They compose: verification pins the loader so only approved code runs; confinement keeps the key out of that code's reach anyway. Verify the prisoner and keep the cage. From there this page climbs the ladder to the strongest tiers — a native attested client, the verifier extension that forces verification on the real bytes, the power-user stack that removes Witbitz from the trust base, and the certified app that makes a closed-source app machine-auditable.

Status. The core chain — pinned key → signed cert → manifest → loader → __vcImport/__vcFetch → bundle + workers + chunks + pinned assets — is built and proven on staging (preview.witbitz-spaces.pages.dev) when forced by the verifier extension / ?vc — the verified client, Implemented, not the default production boot. Its extension-free, origin-served counterpart — the pinned bundle, a self-bootstrapping loader + a signed client manifest that content-pins the shell's imported module closure — is Design, not built. The confinement plane (vault sandwich + Kernel) is live in production. Where a piece is design or device-only it says so.

The one problem it solves

script-src 'self' — the normal same-origin policy — trusts any script the origin serves. That is exactly the wrong thing against a compromised origin: the evil script is same-origin, so 'self' waves it through, and stripping the Subresource-Integrity attributes off the HTML disables the per-file checks. The verified client removes 'self' and replaces "trust the origin" with "trust one small loader whose hash is pinned out-of-band, and let it check everything else."


Part 1 — Verification: what code runs

The trust anchor and the chain

Trust bottoms out at a pinned platform public key (P-256), published in verify it yourself and compiled into the loader and the verifier — never fetched from the app. From there, one signature covers the whole frontend:

code
PINNED platform key  →  signed /cert.json (frontendSha256, ES256)  →  sha256(/assets-manifest.json)  →  every file's hash

A compromised origin can serve an evil bundle, but it cannot forge a cert that verifies against the pinned key, so any swapped byte fails the chain and is refused.

The loader — the small, stable, pinnable entry

spaces/public/space-loader.js is the entry point. It is deliberately tiny and stable across app releases (it imports nothing versioned), so its hash is the one thing an extension has to pin. space.html loads it:

html
<script type="module" src="./space-loader.js" data-app="./space.js" data-bundle="./verified-app.json"></script>

Its exported verifyRelease() runs the chain above (pinned key → cert signature → frontendSha256 == sha256(manifest)) and returns run-or-exit. On a mismatch the app is never imported and the user sees a plain "couldn't verify this app" page instead of running unknown code.

Two tiers

Verification is opt-in per load, so the default path is byte-identical to a normal boot and a stray cert outage can never brick startup.

Tier Trigger What it guarantees
A — verify-in-place ?vc=1 (or the verifier) The served frontend matches the signed release (run-or-exit). The app still loads as normal ES modules under script-src 'self'.
B — loader-only CSP ?vcbundle=1 (or the verifier) script-src '<loaderhash>' '<bundlehash>' … with no 'self'. Evil same-origin JS cannot run at all.

Tier A answers "is this the right release?" Tier B additionally answers "can anything else run?" — no. The verifier extension is what forces either tier so the origin can't opt out.

How Tier B runs a whole app under "no 'self'"

Under a loader-only CSP the browser won't load same-origin <script>s or import() modules — so the loader stops relying on the browser to fetch code and does it itself. It fetches the app as text, checks its hash, and injects it inline — which runs only because that hash is the second pin in script-src. From there the app is a single verified closure. Two reusable primitives extend the same guarantee to everything the app loads lazily:

Workers get the same treatment: the loader pre-verifies each worker's bundle, starts it from a verified blob URL (so worker-src blob: admits only checked bytes), and hands it the signed manifest so the worker can __vcFetch-check anything its runtime pulls in — e.g. the on-device speech model's WebAssembly (code) and weights (data). The build stage tools/build-verified-app.mjs bundles the app and each worker, rewrites new Worker/import() to the verified shims, and writes a signed verified-app.json sidecar.

Third-party assets are frozen, then verified

Large third-party binaries — the transformers.js runtime, the ONNX-runtime WebAssembly, the Whisper model — used to be fetched at deploy from moving CDN/registry refs with no integrity check, so a deploy would sign whatever the CDN served that day. They are now frozen: fetch-whisper.sh verifies every byte against a recorded pin (tools/whisper-assets.sha256) and aborts on drift, and the manifest covers them so __vcFetch can check them at load. The WebAssembly is code and must be verified; the model weights are data (a swap yields a wrong transcription, not code execution) — both are verified here regardless.

The pinned bundle — collapsing the client to one shell

Status: DESIGN, not built. Live today: the egress-lock CSP, the signed reproducible render build, /cert.json, and CSP-sha256 pinning of the shell's inline script. Not yet shipped: extending that pin to the shell's imported module closure via a bootstrap loader + signed client manifest. It is the web-side complement to the attested client and the piece that lets the reproducible-build proof (verify Tests 6–7) cover the code the browser actually executes, not just the render and inline shell.

The gap, precisely. The /space page is a small HTML shell: an inline app module (CSP hash-pinned) plus import statements pulling in ~23 sibling modules — e2ee.js (your encryption), recovery.js, backupVault.js, and the rest. Those imports pin the names of what loads, not the bytes; the modules load under script-src 'self' (same-origin, not content-pinned). So a swapped e2ee.js survives a shell-only check, the egress lock doesn't catch it (it can fold your key into an allow-listed request, or simply weaken the crypto), and an external curl | sha256sum verifies a different fetch than your session (split-view).

This is not self-attestation — it is a bootstrap. A page cannot verify itself. This design does something narrower and achievable: it collapses the client's verifiable surface from ~23 moving files to one small, stable shell by having the shell content-pin everything it loads. It is measured boot for the web. The mechanism: the build signs {path: sha256} for the transitive module closure into /cert.json (shellSha256, closureSha256, modules[]); the shell replaces import './x.js' with loadVerified('./x.js') — fetch once → hash → compare → execute that exact copy via a blob: module URL, else fail-closed (one fetch, verify, run — never re-fetch, or split-view reopens at the module level); rebuilding published source yields the same manifest, so verify Tests 6–7 grow to cover the whole client closure. A swapped module then fails to load rather than silently running. The shell's own bytes still need an external anchor — browser CSP + signed hash today; transparency gossip or a native/extension verifier to fully close it.

Where trust bottoms out, and honest residuals

Root For
The pinned platform key (published, out-of-band) the whole signature chain
The loader's hash (the one thing the verifier pins) that only the real loader is the entry
Math — signatures and SHA-256 every byte check
The browser + extension running faithfully the enforcement itself

Witbitz vouches for nothing about itself that isn't checkable against a key it doesn't control at verification time. The residuals, stated once for the whole page: verification is integrity, not correctness — a genuine bug in honestly-signed code is orthogonal, so receiver hardening (XSS-safe render, sandboxed widgets, validated tool inputs) still matters; without the verifier extension a naked browser has no out-of-band signal that the loader ran honestly (the toolbar badge is what a page can't forge); and attestation protects the code and the key, not the fact that plaintext reaches a model provider — the strongest-privacy tier still needs a BYO or enclave-hosted model (the attested tier).


Part 2 — Confinement: who can touch the key

The verification plane answers what code runs. This plane answers the other half — who can touch the room key — and it is the older, load-bearing idea, live in production: room-key custody lives in an outer frame the app cannot read.

The one idea, and the vault sandwich

Room-key custody moves out of the app frame into an outer frame the app cannot read. A vault (its own origin) holds the account master and the room keyring and frames the keyless app as an iframe. The app boots with no key in its URL and no key in its storage; the keys it needs arrive by a handshake it can't widen.

code
  vault  (L1 — holds the master + room keys, top frame, un-framable)
    │  frames the app with  ?fullapp=1&vcp=1&evictmk=1   (NO key in the URL)
    ▼
  the app  (L2 — separate origin, keyless: its live key variable stays null)
    └── ready{room}  →  key{room, mk}     (postMessage, origin-pinned both ways)
Layer Realm / origin Holds Never holds
L1 vault the vault origin (top frame) account master, room keyring, recovery UI, identity ceremony, the login/account pixels
L1 Kernel an L1 Web Worker (separate heap) room → mk and all mk-crypto full topology, network egress
L2 app the app origin (iframe, keyless) the view-models it renders; its live key variable is null the master, the keyring, the vault's storage

Three inversions versus the naive "put the key in the app's URL" design: the outer frame is the trusted one; the key arrives by handshake, not a URL fragment; the app is a separate origin, so the Same-Origin Policy is the wall, not the app's good behavior. The app is the same build as standalone — a runtime flag (IN_VAULT) selects the confined behavior, and the vault forces evictmk=1 so the current room's key is never a live main-thread variable. The vault is itself un-framable (frame-ancestors 'none' + X-Frame-Options: DENY); the login and account pixels have to live in it, because a cross-origin iframe can't run FedCM / Google sign-in / the recovery overlay — the ceremonies that touch the master.

Why the outer frame, not just discipline. A single-origin app can intend to keep its key from its render code, but nothing enforces it — an XSS or supply-chain slip in any module reads the same-origin localStorage and the live key. Splitting the vault onto its own origin makes the boundary browser-enforced: the app frame's window.top.localStorage throws SecurityError. That is the difference between "we're careful" and "the platform won't let it."

How the app gets keys without holding them. Two paths, both keeping custody outside the app's persistent reach — on demand (boot keyless, post ready{room}, the vault answers key{room, mk} fed to the Kernel worker) and in bulk (the account's index room carries room → mk for every Space; the Kernel absorbs every key from the index doc worker-side, which is why a new device needs only the index-room pointer to open everything). The full app↔vault channel is a small origin-pinned postMessage protocol (vcHandoff), every message gated on the exact peer origin.

The kernel and Atlas

Inside the vault sandwich, the key never sits as a plain variable — it lives in a Kernel: a Web Worker that holds every room key and does all the crypto, taking semantic requests in and never letting a key back out. Atlas is the sibling idea for the metadata (the room graph).

Status. The Kernel is built and, for key-learning from the index, default-on in production. The keyless-index mechanic ships; the full metadata-confinement Atlas realm is a deliberate non-goal for today's threat model and a documented target beyond it.

The organizing principle is least authority by information class — cryptographic secrets ≠ sensitive metadata ≠ presentation ≠ transport — so a compromise of one exposes only its own class:

Part Realm Holds Never holds
Kernel an L1 Worker (separate heap) room → mk, all mk-crypto full topology, network egress
Atlas a sibling L1 Worker the topology graph (rooms, titles, lane/pipe edges) raw keys, network egress
L1 broker the loader's main thread capabilities/ports, opaque ciphertext in transit keys, topology plaintext, view-models
App the L2 iframe the scoped view-models it renders keys, the full graph

The Kernel (l1-worker.js) takes semantic ops — poll, submitTurn, readState, seal, unseal — and does the mk-crypto and entry-signing inside its own realm; the key never posts back out, so a render-layer compromise can ask the Kernel to work but can't extract the material. Stated precisely so it isn't overclaimed: a Worker is a separate JS realm and heap with no direct object-reference access — not physical memory isolation; the boundary is only as strong as what's forbidden across it (no SharedArrayBuffer, no key-bearing transferables returned, no debug interfaces left on).

Atlas is used two ways. The keyless-index mechanic (built, default-on, ?atlasidx): the index room's room → mk pointers would leak keys if surfaced, so the Kernel absorbs them on unseal and returns a keyless graph, and injects them on seal so the app never touches a raw pointer even on write. The full metadata-confinement realm (target, descoped) would give the graph its own Worker and hand the app only keyless projections — closing the residual where a compromised render layer can read your Space graph (titles, lane membership) — but it's an expensive projection-completeness grind, and for the current threat model it's a non-goal: the app is the file browser and may see the directory listing; only the contents' keys are confined.

The strongest anti-exfiltration lever: L1 is the sole egress. Only the L1 broker has network access, and it moves opaque ciphertext it cannot interpret; the Kernel and Atlas can interpret sensitive data but have no ambient network access. So the components that can understand the data cannot send it; the component that can send cannot understand it — which directly closes the covert-channel exfiltration residual. Capability discipline hardens the ports as they mature: scope bound to the port not a parameter (poll() with no room argument, so a caller can't widen authority); explicit revocation (every capability is revocable, a membership/room change bumps the epoch); and bootstrap → LOCK (broad setup interfaces disappear after a short boot phase — privilege-dropping as in OS design). Background notifications must not reintroduce a raw key on the Service Worker: the design derives a one-way notification key NK = HKDF(mk, "notif") scoped to short previews, epoch-rotated and per-device revocable.


Part 3 — The attested client (native)

Status: the server-side seam is Implemented (built + tested, behavior-neutral, not enabled); the native clients are Design. A room's owner-signed policy can carry requireAttestation, and the render verifies a freshness-bound platform attestation against it (bound to the member's signing key so it can't be replayed) — implemented and tested with a mock verifier. What still needs the native side: the reproducible native shells and the real App Attest / Play Integrity / Keystore / TPM verifiers (today stubbed and fail-closed, so a requireAttestation room refuses every turn — the seam is ready for the verifiers to drop in). This is the missing symmetric half of the server attestation the rest of the platform already does.

You cannot verify the code a browser is running — the user owns the runtime; any check a page runs on itself can be hooked, and nothing stops a user from calling the API with their own client. The response is not to attest the browser (unwinnable) but to offer, opt-in, a client whose code can be attested: the exact static, egress-locked, reproducibly-built web bundle, wrapped in a thin native shell whose only jobs are to (1) present a hardware attestation, (2) hold keys in the Secure Enclave / StrongBox / TPM so the room key never sits in JS, (3) load the bundle locally into a locked web view, and (4) freshness-bind the attestation to each action. One source of truth across web and native; the audit surface is a thin generic shell plus the web code reviewers already read.

The one rule that makes it real, not theater: the web bundle must ship inside the attested binary and load locally — never off the network. Bundled and loaded from a local scheme, the app's signature covers its embedded assets, so an attestation of "genuine, unmodified app" includes the bundle. A web view pointed at a live URL (including Android Trusted Web Activities) attests the shell, which then runs whatever the network hands it — worse than a plain browser, because it launders trust it never earned. And the attestation must bind to every action, not once at admission, or a member attests with the genuine app then switches to a patched client for the writes that follow — the room's existing freshness challenge is the hook.

Platform Shell Attestation Keys Notes
Android WebView Play Integrity, or hardware Keystore attestation (X.509 rooted in Google, carries app-signing digest + Verified Boot) StrongBox / TEE Cleanest full chain. Self-distributed + reproducible APK + Keystore attestation gives source → binary → attested.
iOS / iPadOS / macOS WKWebView App Attest (hardware-backed) Secure Enclave Integrity strong; reproducibility partial — Apple re-signs the shipped binary, so you lean on a transparency-logged build pipeline for provenance.
Windows WebView2 Assembled — TPM 2.0 measured boot + Azure Attestation · TPM key attestation · Authenticode/MSIX + optional WDAC TPM 2.0 No single "App Attest for Win32"; strongest on a managed enterprise fleet (MDM + WDAC).

So integrity of the running bundle is attestable on every platform; end-to-end reproducibility of the shipped binary is Android-clean, iOS-partial, Windows-DIY — state that split plainly. The egress lock generalizes: today it is the CSP header; with native it splits into one canonical signed policy (the room-data path goes only to the declared origin; the shell reaches only a small fixed set of platform endpoints — attestation provider, push) and a per-platform enforcement of it. Every native OS control is coarser than connect-src, so on native the fine-grained guarantee leans on the reproducible, attested binary rather than a readable header: on the web you read a header; on native you reproduce and attest the binary. The web is not the weak link in the egress story — it's the platform with the cleanest, self-verifiable lock.

How a room requires it. "Only attested clients may join" is not a client-asserted flag — it's an admission condition set by the room/app owner and verified on the server, in the same slot that carries "only my registered users" (the owner rule). At join and on each write the server checks the attestation, bound to the member's signing key; other members can be shown a participant is on an attested client. Admission and client-integrity become one policy: { authenticated identity } AND { attested client }. It shrinks the hardest residual — content injection between members — without making the client omnipotent-proof (a rooted device, a bug in the genuine app, and untrusted content from others remain, so receiver hardening still matters). Because requiring an install trades away open-a-link reach, it is opt-in and additive: web rooms keep the untrusted-client architecture; attested-native rooms get this stronger tier on top.


Part 4 — The verifier extension

The verified client can check itself, but a naked browser has no out-of-band way to know the check ran honestly — an origin could serve a loader that lies. The verifier is a small browser extension that removes that last assumption: it lives in the toolbar, outside the page, forces verification the origin can't opt out of, pins the loader by hash, and shows a badge the page cannot forge. It ships in the repo at docs/verified-client-real/extension/.

Status. Built and proven headless against the deployed staging app (Chromium, DNR-forced CSP, CDP-confirmed). This is the shipped CSP-loader approach; the transparency-log and enclave extensions that go further are the power-user stack roadmap.

The shape is a per-brand, single-origin verifier whose host_permissions are scoped to one app origin — e.g. a bank's own "Bank X Verified Client." Minimal blast radius, a trivial least-privilege review, and the brand owns the whole chain. The same generic core is just configured with { origin, pinnedKey, bundleMode }. It does two independent jobs, both at the network layer: (1) force verification — a declarativeNetRequest redirect adds ?vc=1/?vcbundle=1 to every /space navigation, so the origin can never serve the app unverified; (2) force the policy — a modifyHeaders rule sets the CSP the renderer sees (the origin's strict policy in Tier A, upgrading a report-only egress-lock to enforced; the loader-only policy in Tier B, no 'self', worker-src blob:). CSP only ever intersects tighter, so a forced strict policy is a floor the origin cannot loosen. Alongside that it independently attests the release — running the same pinned-key → cert → manifest chain itself — and paints a toolbar badge (green ✓ / red !) the page cannot draw.

Why it works on mainstream Chromium (no response-body reading). An earlier design required Firefox and webRequest.filterResponseData to hash the exact bytes the browser loaded (Chromium MV3 can't read response bodies). The CSP-loader model sidesteps that: instead of observing the bytes, the extension forces a CSP that pins the loader's hash, and the loader verifies everything else — there is no byte to race, only the pinned loader can be the entry. That makes it a standard MV3 extension using declarativeNetRequest (applied before the renderer sees the response). The DNR rules are pre-armed at startup so the first navigation is already covered — no uncovered first load, no reload.

Honest residuals. A page's own service worker can synthesize a navigation response that never traverses the network, so the verified policy must include SW disposition (approve a specific SW hash or forbid one), enforced on-origin; the origin-served loader is the cost of keeping the brand's real domain (a chrome-extension:// page would break passkeys, cookies, TLS identity). The badge is the user's ground truth, but a naked browser has the same phishing surface as any site — the extension is the additive apex for people who want to check.

Install the verifier — a five-minute walkthrough

The extension in docs/verified-client-real/extension/ runs today on Chromium (128+) and enforces against the staging app (preview.witbitz-spaces.pages.dev). It's an unpacked developer build, not yet a Web Store listing.

  1. Point it at the app origin. config.json is the entire app-specific surface: origin (the app you want verified), pinnedKey (the platform signing key from verify it yourself — the one trust anchor, compared out-of-band), and bundleMode (true forces Tier B, loader-only CSP; false is Tier A verify-in-place).
  2. Load it unpacked. chrome://extensionsDeveloper mode on → Load unpacked → select the extension/ folder. The service worker starts and pre-arms its rules immediately.
  3. Open a Space and watch it verify. Navigate to …/space. Without doing anything else you see: the URL redirect to …?vcbundle=1 (Tier B) or …?vc=1 (Tier A); the app boot normally (the loader fetched the bundle and workers, checked each hash against the signed manifest, and injected them); the toolbar badge turn green ✓.
  4. Confirm it's really enforcing. Turn the extension off and reload — the URL stays /space and the badge is gone (the contrast is the proof). Inspect the /space response headers with the extension on: a content-security-policy with no 'self' in script-src (Tier B) and the report-only header removed. If the release ever fails the chain — a swapped byte, a bad signature — the loader shows "couldn't verify this app" and the badge goes red.
  5. Verify the extension itself (the one out-of-band act). The whole system hangs on trusting the verifier, so check it once: read its four small files, confirm the pinnedKey matches verify it yourself, and — for the strongest anchor — prefer an extension authored by someone other than the app vendor, so it isn't the vendor vouching for itself. That single check, done once, is the only thing you take on faith.

Part 5 — The power-user stack

Status: TARGET ARCHITECTURE. It composes shipped pieces (signed /cert.json, reproducible render build, readable egress-lock, the mkseal/mkUnseal wire format) with designed ones (the pinned bundle) and net-new ones (a filterResponseData verifier extension, transparency-log publishing + gossip, and the render in an attested enclave — the attested tier). It is the honest answer to "how does a user know they run a good app?" for a user willing to run one extra tool.

Who it's for: the power user who will install Firefox (Android or desktop) and one verifier extension — because a mobile web page alone cannot verify itself and an operator serving its own app proves nothing. The mainstream reach (open-a-link, any browser) keeps the existing model; this is the additive apex. The property that makes it "perfect": one deliberate trust decision, verified out-of-band once — then everything is checked automatically, on the real bytes, from browser chrome, with cheating detectable. Witbitz is removed from the trust base.

The five components: (1) a verifier extension — open-source, reproducible, ideally authored by someone other than Witbitz, trust anchors pinned inside it, using webRequest.filterResponseData in Firefox to hash the exact bytes the browser loaded (the same fetch the page uses — what makes it split-view-proof); (2) the pinned-bundle client; (3) the signed /cert.json (shellSha256, closureSha256, modules[], lambdaCodeSha256, sourceSha256, gitCommit, egress, enclave); (4) transparency logs — every build's hashes to ≥2 independent append-only logs; the extension checks inclusion and gossips signed tree heads, so a split-view is detectable by any one honest observer; (5) the enclave (Nitro) — the render runs attested, the extension verifies PCR0 == the reproduced measurement chaining to the Nitro root.

Per-load flow (automatic after a one-time setup): hash the shell + every module the browser actually loaded → verify /cert.json against the pinned pubkey → loaded hashes == the signed manifest → hashes are in the transparency logs with no split-view → verify the enclave attestation → confirm connect-src == the signed egress → badge green/red. Trust bottoms out entirely outside Witbitz — Mozilla, the independent extension author, the AWS Nitro root, independent transparency logs, and math. Not zero-trust — minimal trust, every deviation detectable and attributable. The likeliest overclaim, stated plainly: attestation protects the key and the code, not the fact that plaintext reaches a model provider — the strongest-privacy tier still needs a BYO/enclave-hosted model. And on iPhone, until the EU's alternative-engine rules give iOS a Firefox-grade extension API, iOS web can't do the byte-level check — an iOS power user's strong path today is the native App Attest app (Part 3).


Part 6 — The certified app

Status: DESIGN / direction. An answer to "how do I know an app is good?" for apps that are not open-sourced, by making the app model small enough that an automated, attested audit is sound. It defines what good means; the power-user stack verifies you received the good build.

Open source + plural human review is the gold standard for "no hidden backdoor," but it demands publishing the source, which most builders won't do. Can you get a verifiable "this app is good" without open-sourcing it? The move: don't out-analyse an adversary's arbitrary code — remove arbitrary code from the problem. A Witbitz app is almost entirely a manifest + UI — a declarative capability request (what data, which tools, where it may send) plus a constrained interface, closer to a policy than a program, and policy is checkable. Complexity disqualifies: an app too complex to audit cleanly is auto-rejected, which kills the main evasion vector for free (obfuscation, time bombs, hidden data-dependent paths are all complexity, and you can't hide a backdoor in convolution if convolution itself fails the gate). This converts "determine what an arbitrary program does" (undecidable) into "check a small declarative artifact against a policy" (tractable, much of it deterministic).

The mechanism: a fixed, confining runtime proven once for all apps (it confines every app to its declared capabilities, keeps the egress-lock, sandboxes the UI, forbids arbitrary code — one small shared artifact, openable and attestable once); plural attested checkers from different entities with different methods, each receiving the source inside an attested enclave (fed to the machines, not the public) and verifying the app matches its privacy certificate, with N-of-M agreement; deterministic where possible (declared egress and capabilities checked with no model), LLM where necessary (does the UI match the stated purpose); and a public verdict log — build hash + privacy certificate + checker set + verdicts — so "N independent attested checkers approved this build" is accountable and challengeable without publishing source. The trust moves from "audit each app's arbitrary code" to "a small fixed runtime + an objective complexity gate + plural attested checkers" — smaller, mostly deterministic, mostly openable. Hold the claim tight: the defensible guarantee is "a simple, capability-confined app, approved by N independent attested checkers against its declared certificate," not "any app is safe."


Next

Machine-readable source: client-architecture.md · Generated 2026-08-29T18:46:30Z · build f3ff88cc · every doc in one fetch: llms-full.txt (HTML) · ← identity-and-admission · the-attested-tier