Cleaned dead code and updated documents

This commit is contained in:
2026-06-04 02:42:13 -04:00
parent 578e124d67
commit ffdf9aa24d
31 changed files with 374 additions and 498 deletions

View File

@@ -9,18 +9,31 @@ Tree organized by role.
```
backends/ server protocol adapters
mizan-django/ Django adapter
mizan-fastapi/ FastAPI adapter (AFI-common scope)
mizan-ts/ TypeScript adapter (proves the protocol is language-agnostic)
frontends/ client kernel + per-framework adapters
mizan-base/ framework-agnostic kernel; owns data, status, error; adapters subscribe
mizan-react/ React contexts + hooks over the kernel
mizan-vue/ Vue composables over the kernel
mizan-svelte/ Svelte stores/runes over the kernel
mizan-django/ Django adapter
mizan-fastapi/ FastAPI adapter (AFI-common scope)
mizan-rust-axum/ Rust/Axum adapter (handlers, errors, IR export)
mizan-tauri/ Tauri adapter — Mizan calls served in-process
mizan-ts/ TypeScript adapter (proves the protocol is language-agnostic)
frontends/ client kernel + per-framework adapters + transports
mizan-base/ framework-agnostic kernel (@mizan/base); owns data, status,
error; adapters subscribe through the MizanTransport interface
mizan-react/ React contexts + hooks over the kernel
mizan-vue/ Vue composables over the kernel
mizan-svelte/ Svelte stores/runes over the kernel
mizan-rust/ Rust client kernel
mizan-tauri-transport/ MizanTransport over Tauri IPC
mizan-webview-transport/ MizanTransport over a webview message channel
mizan-webview-channels/ channel transport over a webview bridge
cores/ shared language-level primitives
mizan-python/ @client decorator, registry, MWT, HMAC cache keys
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
protocol/ protocol-level tooling
mizan-generate/ codegen — schema in, typed client out
mizan-codegen/ codegen — Rust binary (crate `mizan-codegen`); reads KDL IR,
emits typed clients. Targets: stage1, react, vue, svelte,
channels, python, rust. Askama templates under templates/.
mizan-generate/ thin npm-package launcher (bin/launcher.mjs) dispatching to
the compiled mizan-codegen binary per platform
workers/ runtime workers / bridges
mizan-ssr/ Bun subprocess used by the Django template backend
```
@@ -35,11 +48,16 @@ compose.
## Kernel model
The client kernel (`mizan-base`) is the one hard thing. Per-
framework adapters are thin idiomatic wrappers around it. Codegen
emits typed bindings against the framework adapter's surface, not
against the raw kernel — so a React developer gets `useEcho()` and
`<MizanContext>`, a Vue developer gets `useEcho()` composables, a
The client kernel (`@mizan/base`) is the one hard thing. It owns
`ContextState<T> = {data, status, error}`, the context registry
(`registerContext`), `mizanCall` / `mizanFetch`, server-driven `merge`
and `invalidate`, and `initSession`. It reaches the backend through a
pluggable `MizanTransport` (`call` / `fetch`); the default is the
HTTP `httpTransport()`, swapped via `configure({ transport })` for
Tauri / webview hosts. Per-framework adapters are thin idiomatic
wrappers that subscribe to the kernel. Codegen emits typed bindings
against the framework adapter's surface — a React developer gets
`useEcho()` hooks, a Vue developer gets `useEcho()` composables, a
Svelte developer gets readable stores. Same kernel underneath.
## KDL is the IR
@@ -56,20 +74,23 @@ divergence between adapters is what the IR exists to prevent.
Forward-direction primitives:
- `cores/mizan-python` builds the IR from registered functions
(`build_ir()` walks `mizan_core.registry`, emits KDL)
- A `mizan-schema` package (forthcoming) holds the canonical KDL
grammar / type system definition that every adapter targets
- Each backend adapter emits KDL on stdout from an IR-export command:
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` /
`context` / `channel` nodes) is parsed by `mizan-codegen`'s
`src/ir.rs`; fixtures live at
`protocol/mizan-codegen/tests/fixtures/*.kdl`.
- `protocol/mizan-codegen/src/fetch.rs` spawns the configured source
command and parses the KDL it writes.
- Codegen reads KDL directly — no OpenAPI envelope, no
`openapi-typescript`, no per-backend converter divergence
- Edge manifest, MWT claims, and other protocol artifacts all derive
from the same KDL
**Current implementation is transitional.** Today the codegen consumes
OpenAPI 3.0 (`x-mizan-functions` + `x-mizan-contexts` extensions over
Pydantic→JSON-Schema), produced via Django Ninja or FastAPI's native
generator. That layered indirection is what introduces adapter
divergence (see the AFI conformance suite). KDL-as-IR collapses it.
`openapi-typescript`, no per-backend converter divergence. The
former JavaScript/Node two-stage codegen (`openapi-typescript` plus
`.mjs` adapters) has been deleted; codegen is now the single Rust
binary.
- Edge manifest, MWT claims, and other protocol artifacts derive from
the same registry/IR.
## Launch surface

View File

@@ -16,14 +16,22 @@ standardized replacement exists.
## Resolution: HMAC cache key (JSON-canonical form)
```
HMAC-SHA256(secret, JSON.stringify({
ctx:{context}:HMAC-SHA256(secret, json.dumps({
"c": context,
"p": sorted_params,
"p": sorted_params, // values normalized to JSON-native strings
"r": rev,
"u": user_id // omitted for public content
}, sort_keys=True))
"u": user_id // omitted for public content
}, sort_keys=True, separators=(",", ":")))
```
`derive_cache_key(secret, context, params, user_id=None, rev=0)`
`"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.
### Key derivation rules
- **Public content** — URL path + query params (standard CDN).
@@ -45,20 +53,24 @@ Mizan claims on `X-Mizan-Token` header. Replaces the old
**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, backed by Redis.
implements the cache protocol in its own language.
**Conformance verified by a shared test suite.**
### Required operations
- `cache_get`
- `cache_put`
- `cache_purge`
- `cache_purge_user`
- `cache_purge` (scoped recomputes the key; broad SCANs the
`ctx:{context}:*` prefix)
### Storage
Redis only. Handles persistence, cross-worker sharing, crash
recovery.
Two backends behind a `CacheBackend` protocol
(`mizan_core/cache/backend.py`):
- `MemoryCache` — dict-based, for testing.
- `RedisCache` — production; persistence, cross-worker sharing, crash
recovery. Broad purge via SCAN, delete via UNLINK.
## Deploy invalidation

View File

@@ -15,8 +15,10 @@ detection.
| `pkey` | Deterministic hash of user's permission state at issuance |
| `exp` | Configurable short TTL — controls permission staleness window (Django setting) |
| `iat` | Issued at |
| `kid` | Key ID — for secret rotation |
| `kid` | Key ID — for secret rotation. Carried in the JOSE header (RFC 7515), not the payload |
| `aud` | Audience binding — prevents cross-tenant replay |
| `nbf` | Not-before — tolerates clock skew |
| `staff` / `super` | `is_staff` / `is_superuser`, used to build `MWTUser` without a DB query |
## Key decisions
@@ -25,10 +27,14 @@ detection.
- **`X-Mizan-Token` header, not `Authorization: Bearer`.** Avoids
collision with DRF, allauth, and existing JWT systems. Cloudflare
WAF/Access do not inspect custom headers.
- **Replaces `JWTUser` + `_try_jwt_auth` entirely.** Old approach is
deleted.
- **`MWTUser`** is a minimal, DB-free request user built from the
token claims (`cores/mizan-python/src/mizan_core/mwt.py`).
> A separate JWT module (`mizan/jwt/`) still exists for standard
> user-auth access/refresh tokens; MWT is the cache-keying identity
> layer, not a replacement for that module.
- **App handles authentication** (session, social, etc.). Mizan
issues MWT *from* the authenticated identity.
issues MWT *from* the authenticated identity
(`create_mwt(user, secret, ttl, audience, kid)`).
- **Edge Worker** validates MWT, extracts `sub` for HMAC cache key,
checks `exp`.
- **`pkey` computation must be deterministic:**
@@ -43,9 +49,11 @@ detection.
JSON with sorted keys:
```
HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "u": user_id}))
ctx:{context}:HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "r": rev, "u": user_id}))
```
See [CACHE_KEYING.md](CACHE_KEYING.md) for the full derivation.
## What this solves
- DRF token collision
@@ -55,5 +63,8 @@ HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "u": user_id}))
## Usage rule
All cache-layer auth code uses MWT, not Django session or raw JWT.
The `@client(auth=...)` parameter gates on MWT validity.
MWT is the identity Edge/cache layers key on. The `@client(auth=...)`
parameter is enforced server-side in `mizan/client/executor.py`
(`_check_auth_requirement`), which checks `request.user` against the
auth requirement (`required` / `staff` / `superuser` / callable);
`request.user` may be an `MWTUser` (stateless) or a session user.

View File

@@ -22,17 +22,16 @@ multi-state privacy. ~$58K legal costs.
TS "Deploy" exists via Workers for Platforms at no additional
compliance cost.
## Free framework: mizan-cache (origin-side cache)
## Free framework: origin-side cache (`mizan.cache`)
Python package implementing the **full cache protocol locally**
same HMAC key derivation, metadata schema, and purge semantics as
Edge.
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.
Three backends:
Two backends behind a `CacheBackend` protocol:
- In-memory dict (default)
- Redis
- SQLite
- `MemoryCache` — in-memory dict (testing)
- `RedisCache` — production
### Dual purpose
@@ -44,8 +43,8 @@ Three backends:
## Spec additions
- `@client(cache=False)` — uncacheable; emits `Cache-Control: no-store`.
- Cache ABI: `get(key)`, `put(key, response, metadata)`,
`purge(context, params)`.
- Cache ABI (`mizan.cache`): `cache_get(secret, backend, context, params)`,
`cache_put(...)`, `cache_purge(backend, context, params=…, secret=…)`.
## Launch compliance (Render only)

View File

@@ -14,6 +14,15 @@ 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.
## Edge Delivery — Mizan Render (Paid Product)
Pre-rendered HTML cached globally on Cloudflare CDN.

View File

@@ -10,23 +10,31 @@ rendering engine.
TEMPLATES = [
{
'BACKEND': 'mizan.ssr.MizanTemplates',
...
'DIRS': [BASE_DIR / 'frontend'],
'OPTIONS': {
'worker': 'path/to/mizan-ssr/src/worker.tsx',
'timeout': 5,
},
}
]
```
Then `render(request, 'ProfilePage', context)` calls the Bun
Then `render(request, 'components/Hello.tsx', context)` calls the Bun
subprocess bridge instead of rendering a Django/Jinja2 template.
**The component name IS the template name.**
**The template name IS a `.tsx`/`.jsx` file path**, resolved against
`DIRS`; `get_template` returns a `MizanTemplate` wrapping the absolute
file path. The context dict becomes the component's props (`request`
and `csrf_token` stripped). Rendered output is wrapped in
`<div id="mizan-root">…</div>` plus a
`<script>window.__MIZAN_SSR_DATA__=…</script>` hydration payload.
## AFI boundary
| Side | Responsibility |
|---|---|
| Backend adapter | Implements `mizan.ssr()` — executes context functions, gathers data |
| Frontend adapter | Implements `renderToHTML()` — takes component + props, produces HTML |
| Bun subprocess | Hosts the frontend adapter |
| stdin/stdout JSON-RPC | Transport between the two |
| Backend adapter (`SSRBridge`) | Manages the Bun subprocess lifecycle; gathers props |
| Bun worker (`worker.tsx`) | `import()`s the file path, `renderToString(createElement(Component, props))` |
| stdin/stdout JSON-RPC | Newline-delimited; `{id, method:"render", params:{file, props}}``{id, html}` / `{id, error}`; `ping``{id, pong:true}` |
## Why template backend
@@ -35,16 +43,22 @@ subprocess bridge instead of rendering a Django/Jinja2 template.
- Django developers already use `render(request, template, context)`
— no new API to learn.
- URL routing, views, middleware, auth — all unchanged.
- The template tag `{% mizan_render %}` is a convenience for
developers who *also* use Django templates (e.g., a base.html shell
with Mizan components inside).
> A `templatetags/` package exists for a future `{% mizan_render %}`
> convenience tag (base.html shell with Mizan components inside), but
> it is currently empty — no tag is implemented yet.
## Implementation surface
The SSR bridge module implements Django's template backend interface:
The SSR backend (`mizan/ssr/backend.py`) implements Django's template
backend interface:
- `BaseEngine` subclass
- `Template` class with `.render(context, request)`
- `MizanTemplates(BaseEngine)` — requires `OPTIONS['worker']` (path to
`worker.tsx`); `get_template(name)` resolves a file under `DIRS`
- `MizanTemplate` with `.render(context, request)` → calls the bridge
- `SSRBridge` (`bridge.py`) — spawns `bun run <worker>`, holds the
persistent subprocess, correlates requests by message id, thread-safe,
auto-restarts on crash, waits for the worker's ready signal
Everything Django expects from a template backend, but the actual
rendering routes to Bun.