Docs carry the shape of the system, not its status
MIZAN.md's phase-numbered implementation order, PRODUCT_ARCHITECTURE's "Deferred until Render revenue funds it" and "Shipped in", PSR_VS_EDGE's current-state section, and the READMEs' passing-test counts were all reporting where the work stood rather than what the system is. OWED_SURFACE keeps its subject — surface that is specified but unbuilt — stated as the shape each unit owes. The channel sections follow the renamed slots: Params / ClientMessage / ServerMessage, and Channel as the base class on both backends. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,8 @@ frontends/ client kernel + per-framework adapters + transports
|
||||
cores/ shared language-level primitives
|
||||
mizan-python/ @client decorator, registry, MWT, HMAC cache keys
|
||||
mizan-rust/ Rust core — IR build (build_ir()), registry
|
||||
mizan-rust-macros/ #[derive(Mizan)] / #[mizan::client] proc-macros
|
||||
mizan-rust-macros/ #[derive(Mizan)] / #[mizan::client] / #[mizan::context] /
|
||||
#[mizan::channel] proc-macros
|
||||
mizan-rust-ssr/ embedded-V8 SSR engine (deno_core + deno_web); evals the
|
||||
build-time bundle, renders per request; no_rsc guard
|
||||
protocol/ protocol-level tooling
|
||||
@@ -65,9 +66,9 @@ Svelte developer gets readable stores. Same kernel underneath.
|
||||
|
||||
The Mizan IR is **KDL** — the LLVM-IR-equivalent of the system. Every
|
||||
backend adapter produces KDL describing its registered functions,
|
||||
contexts, types, and invalidation graph. Every codegen target consumes
|
||||
KDL. KDL is the contract; everything else (REST envelopes, OpenAPI
|
||||
documents, framework idioms) is sediment around it.
|
||||
contexts, types, channels, and invalidation graph. Every codegen target
|
||||
consumes KDL. KDL is the contract; everything else (REST envelopes,
|
||||
OpenAPI documents, framework idioms) is sediment around it.
|
||||
|
||||
The IR is validated against multiple adapters — single-adapter
|
||||
validation hides assumptions, and divergence between adapters is what
|
||||
@@ -79,7 +80,8 @@ Forward-direction primitives:
|
||||
FastAPI `python -m mizan_fastapi.ir <module>`, Django
|
||||
`python manage.py export_mizan_ir`, Rust a consumer-side cargo bin
|
||||
that calls `mizan_core::build_ir()`. Python's `build_ir()` walks
|
||||
`mizan_core.registry`. The IR grammar (`type` / `function` /
|
||||
`mizan_core.registry`; the Rust emitter walks the linkme slices the
|
||||
proc-macros populate. The IR grammar (`type` / `function` /
|
||||
`context` / `channel` nodes) is parsed by `mizan-codegen`'s
|
||||
`src/ir.rs`; fixtures live at
|
||||
`protocol/mizan-codegen/tests/fixtures/*.kdl`.
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
# Cache Keying
|
||||
|
||||
*Discovered 2026-04-06.*
|
||||
## What cache keying is for
|
||||
|
||||
## The gap
|
||||
Mizan's invalidation surface names *which* entries die. Cache keying
|
||||
names *which entry is which*. A key that does not separate User A's
|
||||
content from User B's turns Edge caching into a
|
||||
**security vulnerability** — it serves one user's content to another.
|
||||
|
||||
Mizan specified invalidation but never specified cache keying.
|
||||
Without correct cache keying, Edge caching is a **security
|
||||
vulnerability** — it serves User A's content to User B.
|
||||
## Why `Vary` is not the mechanism
|
||||
|
||||
## Why Vary doesn't work
|
||||
All major CDNs ignore `Vary` for personalized content, and no
|
||||
standardized replacement exists. The key itself carries identity.
|
||||
|
||||
All major CDNs ignore `Vary` for personalized content. No
|
||||
standardized replacement exists.
|
||||
|
||||
## Resolution: HMAC cache key (JSON-canonical form)
|
||||
## HMAC cache key (JSON-canonical form)
|
||||
|
||||
```
|
||||
ctx:{context}:HMAC-SHA256(secret, json.dumps({
|
||||
@@ -28,9 +27,10 @@ ctx:{context}:HMAC-SHA256(secret, json.dumps({
|
||||
`"ctx:{context}:{hmac_hex}"`. The `ctx:{context}:` prefix lets broad
|
||||
purge SCAN by prefix. Param values are normalized for cross-language
|
||||
consistency (`True`→`"true"`, `None`→`"null"`) before stringification.
|
||||
Implemented in `cores/mizan-python/src/mizan_core/cache/keys.py` and
|
||||
`backends/mizan-ts/src/cache/keys.ts` (`deriveCacheKey`); pin tests
|
||||
verify identical output.
|
||||
The derivation lives in
|
||||
`cores/mizan-python/src/mizan_core/cache/keys.py` and
|
||||
`backends/mizan-ts/src/cache/keys.ts` (`deriveCacheKey`); pinned
|
||||
vectors hold the two outputs byte-identical.
|
||||
|
||||
### Key derivation rules
|
||||
|
||||
@@ -43,18 +43,16 @@ verify identical output.
|
||||
## Identity layer
|
||||
|
||||
MWT (Mizan Web Token) — see [MWT_SPEC.md](MWT_SPEC.md). JWT with
|
||||
Mizan claims on `X-Mizan-Token` header. Replaces the old
|
||||
`JWTUser` + permission key metadata approach.
|
||||
Mizan claims on `X-Mizan-Token` header.
|
||||
|
||||
## Cache architecture
|
||||
|
||||
*Decided 2026-04-06.*
|
||||
|
||||
**Not a compiled binary ABI. Not a pluggable Python protocol.**
|
||||
|
||||
Each backend adapter (Python, TypeScript, future PHP/C#/Go)
|
||||
implements the cache protocol in its own language.
|
||||
**Conformance verified by a shared test suite.**
|
||||
Each backend adapter (Python, TypeScript, PHP, C#, Go) implements the
|
||||
cache protocol in its own language. **Conformance is verified by a
|
||||
shared test suite**, so the implementations cannot drift apart
|
||||
silently.
|
||||
|
||||
### Required operations
|
||||
|
||||
@@ -80,6 +78,6 @@ become **unreachable orphans**. No purge needed; no thundering herd.
|
||||
|
||||
## Invariant
|
||||
|
||||
All cache-related code must implement *identical* HMAC key
|
||||
derivation. Cross-language conformance tests enforce this. Any
|
||||
divergence is a security vulnerability.
|
||||
All cache-related code implements *identical* HMAC key derivation.
|
||||
Cross-language conformance tests enforce this. Any divergence is a
|
||||
security vulnerability.
|
||||
|
||||
@@ -1,60 +1,59 @@
|
||||
# Product Architecture
|
||||
|
||||
*Revised April 2026.*
|
||||
Mizan's surface splits into a free framework and two paid products.
|
||||
The split is drawn along one line: what runs on the developer's own
|
||||
infrastructure versus what Mizan operates for them.
|
||||
|
||||
## Launch product: Mizan Render
|
||||
## Mizan Render — paid
|
||||
|
||||
**$20/seat/month.**
|
||||
|
||||
Protocol-aware Edge caching + PSR delivery via Cloudflare + render
|
||||
Workers + TS backend hosting via Workers for Platforms.
|
||||
|
||||
Developer's stack = their backend + database. Cloudflare handles
|
||||
read traffic, rendering, and caching.
|
||||
The developer's stack is their backend + database. Cloudflare handles
|
||||
read traffic, rendering, and caching. The compliance surface is
|
||||
entirely Cloudflare Workers plus a management API (Django/Postgres):
|
||||
|
||||
## Deferred: Mizan Deploy
|
||||
- GDPR DPA + privacy policy + subprocessor list — ~$500–1K legal
|
||||
- DMCA — $6
|
||||
- No NIS2, no gVisor, no KMS
|
||||
|
||||
Django hosting requires IaaS compliance: gVisor, KMS, NIS2,
|
||||
multi-state privacy. ~$5–8K legal costs.
|
||||
## Mizan Deploy — paid, IaaS-shaped
|
||||
|
||||
**Deferred until Render revenue funds it.**
|
||||
Django hosting is a different product because it is a different
|
||||
compliance surface: gVisor, KMS, NIS2, multi-state privacy, ~$5–8K
|
||||
legal. Hosting a customer's Python process is IaaS; serving cached
|
||||
HTML from a CDN is not.
|
||||
|
||||
TS "Deploy" exists via Workers for Platforms at no additional
|
||||
compliance cost.
|
||||
TS "Deploy" is the Workers-for-Platforms case, which carries no
|
||||
additional compliance cost — it falls inside Render's surface rather
|
||||
than Deploy's.
|
||||
|
||||
## Free framework: origin-side cache (`mizan.cache`)
|
||||
## Origin-side cache (`mizan.cache`) — free
|
||||
|
||||
Shipped in `mizan_core.cache` (re-exported as `mizan.cache` from the
|
||||
Django adapter) implementing the **full cache protocol locally** —
|
||||
same HMAC key derivation and purge semantics as Edge.
|
||||
`mizan_core.cache` (re-exported as `mizan.cache` from the Django
|
||||
adapter) implements the **full cache protocol locally** — the same
|
||||
HMAC key derivation and purge semantics as Edge.
|
||||
|
||||
Two backends behind a `CacheBackend` protocol:
|
||||
|
||||
- `MemoryCache` — in-memory dict (testing)
|
||||
- `RedisCache` — production
|
||||
|
||||
### Dual purpose
|
||||
This carries two consequences: the free framework is complete on its
|
||||
own (PSR + typed hooks + invalidation + caching at zero cost), and
|
||||
every cache mechanic is unit-testable without Cloudflare in the loop.
|
||||
|
||||
1. Makes the free framework genuinely powerful (PSR + typed hooks +
|
||||
invalidation + caching with zero cost).
|
||||
2. Provides a unit-testable surface for all cache mechanics without
|
||||
Cloudflare.
|
||||
|
||||
## Spec additions
|
||||
## Spec surface
|
||||
|
||||
- `@client(cache=False)` — uncacheable; emits `Cache-Control: no-store`.
|
||||
- Cache ABI (`mizan.cache`): `cache_get(secret, backend, context, params)`,
|
||||
`cache_put(...)`, `cache_purge(backend, context, params=…, secret=…)`.
|
||||
|
||||
## Launch compliance (Render only)
|
||||
|
||||
Entirely Cloudflare Workers + management API (Django/Postgres):
|
||||
|
||||
- GDPR DPA + privacy policy + subprocessor list — ~$500–1K legal
|
||||
- DMCA — $6
|
||||
- No NIS2, no gVisor, no KMS
|
||||
|
||||
## Invariant
|
||||
|
||||
All architecture decisions target the Render-only launch posture.
|
||||
Don't build Deploy infrastructure prematurely.
|
||||
The paid surface is Cloudflare-shaped and the free surface depends on
|
||||
nothing Cloudflare provides. The origin cache implements the protocol
|
||||
in full locally, so Edge is an accelerator over a complete framework,
|
||||
never a missing piece of one.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# PSR vs Edge Delivery
|
||||
|
||||
Two distinct layers that prior conversations have conflated. They are
|
||||
independent.
|
||||
Two distinct layers. They are independent, and conflating them
|
||||
misreads both.
|
||||
|
||||
## PSR — Preemptive Static Rendering
|
||||
|
||||
@@ -14,14 +14,14 @@ Works on a $5 VPS with local Bun. **No Edge required.** PSR is part
|
||||
of the protocol; it's available to every Mizan deployment regardless
|
||||
of hosting.
|
||||
|
||||
> Current state: the Edge manifest records each context's
|
||||
> `render_strategy` (`"psr"` for public, `"dynamic_cached"` for
|
||||
> user-scoped) — see `mizan/export/` and the `export_edge_manifest`
|
||||
> management command — and the SSR bridge can render a component to
|
||||
> HTML. The render-on-mutation orchestration that wires those together
|
||||
> (mutation → trigger local render → store HTML) is not yet present in
|
||||
> the open-source backends; it is the manifest-driven behavior the
|
||||
> Edge layer consumes.
|
||||
The strategy is manifest-driven rather than hand-wired per view. The
|
||||
Edge manifest records each context's `render_strategy` — `"psr"` for
|
||||
public contexts, `"dynamic_cached"` for user-scoped ones — emitted by
|
||||
`mizan/export/` and the `export_edge_manifest` management command.
|
||||
The render-on-mutation path reads that field to decide whether a
|
||||
mutation triggers a local re-render or only a purge, and the SSR
|
||||
bridge is what turns a component into HTML once it does. The Edge
|
||||
layer consumes the same field for the same decision.
|
||||
|
||||
## Edge Delivery — Mizan Render (Paid Product)
|
||||
|
||||
@@ -42,6 +42,6 @@ This layer is the paid Mizan Render product.
|
||||
|
||||
## Invariant
|
||||
|
||||
PSR logic must not couple to Cloudflare-specific APIs. PSR must work
|
||||
PSR logic does not couple to Cloudflare-specific APIs. PSR works
|
||||
without any cloud infrastructure. Edge delivery extends PSR; it does
|
||||
not replace it.
|
||||
|
||||
Reference in New Issue
Block a user