Restore documentation layer — match current substrate
ROADMAP: done items moved out of "Next" (codegen rewrite, SSR bridge, edge manifest, X-Mizan-Invalidate, return-type branching, affects_params, kernel extraction, two-stage codegen, mizan-ts). Real "Next" in: framework-adapter wrapper layer (MizanContext + useMizan + DjangoError on top of the kernel) for React/Vue/Svelte; A1–A4 from ISSUES.md. CLAUDE: 4-package layout replaced with the actual 7-package layered architecture (backend protocol adapters + frontend kernel + framework adapters + SSR worker). "STALE codegen" section rewritten to describe what's emitted vs. the wrapper layer that isn't yet. docs/ now tracked (6 files). AFI_ARCHITECTURE rewritten — replaced the speculative `mizan-ast`/`mizan-csr`/`mizan-rpc`/`mizan-schema` package names with the real layout, dropped KDL-schema language for the actual schema-export format. The other 5 docs/ files were already current and are tracked as-is. ARCHITECTURE-REWORK.md deleted — same expert review is re-tracked in the fresher ISSUES.md, two parallel trackers was sediment. README.md deleted — drift was beyond surgical fixes (`mizan_clients.py` convention, `<DjangoContext>` provider, removed `@compose` and `context='local'`, wrong codegen output filenames, 3-package structure vs. 7). Rewrite waits for the wrapper-layer codegen to land so user-facing examples reflect reality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
docs/AFI_ARCHITECTURE.md
Normal file
77
docs/AFI_ARCHITECTURE.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# AFI Architecture
|
||||
|
||||
Mizan is an **Application Framework Interface (AFI)** — the
|
||||
server-client unification layer.
|
||||
|
||||
## Package layout
|
||||
|
||||
Two layers per side. Independent packages, single shared protocol.
|
||||
|
||||
**Backend protocol adapters** — implement the wire protocol on a
|
||||
server stack:
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `mizan-django` | Django adapter |
|
||||
| `mizan-ts` | TypeScript adapter (proves the protocol is language-agnostic) |
|
||||
|
||||
**Frontend kernel + framework adapters** — the kernel is the
|
||||
imperative client primitive set; each framework adapter wraps the
|
||||
kernel in its own idiomatic constructs:
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `mizan-runtime` | Framework-agnostic client 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 |
|
||||
|
||||
**SSR worker:**
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `mizan-ssr` | Bun subprocess used by the Django template backend |
|
||||
|
||||
## Two orthogonal products
|
||||
|
||||
- **RPC** — typed client generation via codegen
|
||||
- **SSR** — server rendering via the Bun bridge
|
||||
|
||||
Independent and composable. Either ships standalone; together they
|
||||
compose.
|
||||
|
||||
## Kernel model
|
||||
|
||||
The client kernel (`mizan-runtime`) 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
|
||||
Svelte developer gets readable stores. Same kernel underneath.
|
||||
|
||||
## Schema is load-bearing
|
||||
|
||||
The backend exports a JSON schema describing every `@client`-decorated
|
||||
function and context (`x-mizan-functions`, `x-mizan-contexts`). The
|
||||
schema IS the contract: codegen reads it, the edge manifest derives
|
||||
from it, MWT auth gates against it.
|
||||
|
||||
## Launch surface
|
||||
|
||||
Python (Django) + React. Vue and Svelte ship as v1 alongside React.
|
||||
TypeScript backend (`mizan-ts`) proves the protocol is portable.
|
||||
|
||||
## Why the AFI shape
|
||||
|
||||
Quadratic ecosystem growth (N server adapters × M client adapters)
|
||||
collapses to linear (one adapter per stack) when both sides
|
||||
communicate through a shared protocol.
|
||||
|
||||
## Invariants
|
||||
|
||||
- All cross-package communication goes through the protocol. No
|
||||
direct cross-package dependencies.
|
||||
- New adapters land as new packages, not as modifications to existing
|
||||
ones.
|
||||
- Framework adapters wrap the kernel in framework idioms — they
|
||||
don't bypass it. Codegen targets the adapter, not the raw kernel.
|
||||
73
docs/CACHE_KEYING.md
Normal file
73
docs/CACHE_KEYING.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Cache Keying
|
||||
|
||||
*Discovered 2026-04-06.*
|
||||
|
||||
## The gap
|
||||
|
||||
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 doesn't work
|
||||
|
||||
All major CDNs ignore `Vary` for personalized content. No
|
||||
standardized replacement exists.
|
||||
|
||||
## Resolution: HMAC cache key (JSON-canonical form)
|
||||
|
||||
```
|
||||
HMAC-SHA256(secret, JSON.stringify({
|
||||
"c": context,
|
||||
"p": sorted_params,
|
||||
"r": rev,
|
||||
"u": user_id // omitted for public content
|
||||
}, sort_keys=True))
|
||||
```
|
||||
|
||||
### Key derivation rules
|
||||
|
||||
- **Public content** — URL path + query params (standard CDN).
|
||||
- **User-scoped content** — HMAC key derivation above.
|
||||
- **`@client(auth=...)`** determines whether content is user-scoped.
|
||||
- **`rev` parameter** on `@client` for deploy-time logic
|
||||
invalidation. Bumped by the developer when function logic changes.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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, backed by Redis.
|
||||
**Conformance verified by a shared test suite.**
|
||||
|
||||
### Required operations
|
||||
|
||||
- `cache_get`
|
||||
- `cache_put`
|
||||
- `cache_purge`
|
||||
- `cache_purge_user`
|
||||
|
||||
### Storage
|
||||
|
||||
Redis only. Handles persistence, cross-worker sharing, crash
|
||||
recovery.
|
||||
|
||||
## Deploy invalidation
|
||||
|
||||
No full context flush. The `rev` parameter on `@client` is part of
|
||||
the HMAC key. When the developer bumps `rev`, old cache entries
|
||||
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.
|
||||
59
docs/MWT_SPEC.md
Normal file
59
docs/MWT_SPEC.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# MWT — Mizan Web Token
|
||||
|
||||
*Decided 2026-04-06.*
|
||||
|
||||
MWT is a standard JWT (RFC 7519, HMAC-SHA256) with Mizan-specific
|
||||
claims, traveling on the `X-Mizan-Token` header. It is the
|
||||
protocol's identity layer for cache keying and permission staleness
|
||||
detection.
|
||||
|
||||
## Claims
|
||||
|
||||
| Claim | Purpose |
|
||||
|---|---|
|
||||
| `sub` | User ID — goes into HMAC cache key derivation |
|
||||
| `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 |
|
||||
| `aud` | Audience binding — prevents cross-tenant replay |
|
||||
|
||||
## Key decisions
|
||||
|
||||
- **Standard JWT envelope, not proprietary.** Uses standard libraries
|
||||
for signing and validation.
|
||||
- **`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.
|
||||
- **App handles authentication** (session, social, etc.). Mizan
|
||||
issues MWT *from* the authenticated identity.
|
||||
- **Edge Worker** validates MWT, extracts `sub` for HMAC cache key,
|
||||
checks `exp`.
|
||||
- **`pkey` computation must be deterministic:**
|
||||
`sorted(user.get_all_permissions())` then hash.
|
||||
- **Client-side: proactive refresh before expiry.** Check TTL before
|
||||
dispatch, not reactively after a 401.
|
||||
- **Header-based, not cookie-based.** A cookie would force
|
||||
`Vary: Cookie`, destroying PSR cache.
|
||||
|
||||
## HMAC canonical form
|
||||
|
||||
JSON with sorted keys:
|
||||
|
||||
```
|
||||
HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "u": user_id}))
|
||||
```
|
||||
|
||||
## What this solves
|
||||
|
||||
- DRF token collision
|
||||
- `JWTUser`-too-thin problem
|
||||
- Permission staleness race condition
|
||||
- Single validation path across Python and TypeScript Edge
|
||||
|
||||
## Usage rule
|
||||
|
||||
All cache-layer auth code uses MWT, not Django session or raw JWT.
|
||||
The `@client(auth=...)` parameter gates on MWT validity.
|
||||
61
docs/PRODUCT_ARCHITECTURE.md
Normal file
61
docs/PRODUCT_ARCHITECTURE.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Product Architecture
|
||||
|
||||
*Revised April 2026.*
|
||||
|
||||
## Launch product: Mizan Render
|
||||
|
||||
**$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.
|
||||
|
||||
## Deferred: Mizan Deploy
|
||||
|
||||
Django hosting requires IaaS compliance: gVisor, KMS, NIS2,
|
||||
multi-state privacy. ~$5–8K legal costs.
|
||||
|
||||
**Deferred until Render revenue funds it.**
|
||||
|
||||
TS "Deploy" exists via Workers for Platforms at no additional
|
||||
compliance cost.
|
||||
|
||||
## Free framework: mizan-cache (origin-side cache)
|
||||
|
||||
Python package implementing the **full cache protocol locally** —
|
||||
same HMAC key derivation, metadata schema, and purge semantics as
|
||||
Edge.
|
||||
|
||||
Three backends:
|
||||
|
||||
- In-memory dict (default)
|
||||
- Redis
|
||||
- SQLite
|
||||
|
||||
### Dual purpose
|
||||
|
||||
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
|
||||
|
||||
- `@client(cache=False)` — uncacheable; emits `Cache-Control: no-store`.
|
||||
- Cache ABI: `get(key)`, `put(key, response, metadata)`,
|
||||
`purge(context, params)`.
|
||||
|
||||
## 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.
|
||||
38
docs/PSR_VS_EDGE.md
Normal file
38
docs/PSR_VS_EDGE.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# PSR vs Edge Delivery
|
||||
|
||||
Two distinct layers that prior conversations have conflated. They are
|
||||
independent.
|
||||
|
||||
## PSR — Preemptive Static Rendering
|
||||
|
||||
**Protocol feature.** Render HTML on mutation, not on request.
|
||||
|
||||
Mechanism: `@client` fires mutation → backend adapter triggers local
|
||||
render runtime → HTML stored locally.
|
||||
|
||||
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.
|
||||
|
||||
## Edge Delivery — Mizan Render (Paid Product)
|
||||
|
||||
Pre-rendered HTML cached globally on Cloudflare CDN.
|
||||
|
||||
Uses `fetch()` to a render Worker on a separate domain
|
||||
(`render.mizan.cloud`) instead of `cache.put()`, because Workers
|
||||
Cache API is per-datacenter only. `fetch()` across zones goes through
|
||||
the global CDN cache path with Tiered Cache.
|
||||
|
||||
This layer is the paid Mizan Render product.
|
||||
|
||||
## Caching modes
|
||||
|
||||
- **Public content** — preemptive (render on mutation)
|
||||
- **User-scoped content** — reactive only (purge on mutation, render
|
||||
on next request)
|
||||
|
||||
## Invariant
|
||||
|
||||
PSR logic must not couple to Cloudflare-specific APIs. PSR must work
|
||||
without any cloud infrastructure. Edge delivery extends PSR; it does
|
||||
not replace it.
|
||||
50
docs/SSR_ARCHITECTURE.md
Normal file
50
docs/SSR_ARCHITECTURE.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# SSR Architecture
|
||||
|
||||
*Decided 2026-04-07.*
|
||||
|
||||
Mizan's SSR adapter is a **Django template backend**. It plugs into
|
||||
Django's existing `TEMPLATES` setting, replacing the template
|
||||
rendering engine.
|
||||
|
||||
```python
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'mizan.ssr.MizanTemplates',
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Then `render(request, 'ProfilePage', context)` calls the Bun
|
||||
subprocess bridge instead of rendering a Django/Jinja2 template.
|
||||
**The component name IS the template name.**
|
||||
|
||||
## 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 |
|
||||
|
||||
## Why template backend
|
||||
|
||||
- Django's template system is swappable by design (batteries
|
||||
included, but replaceable).
|
||||
- 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).
|
||||
|
||||
## Implementation surface
|
||||
|
||||
The SSR bridge module implements Django's template backend interface:
|
||||
|
||||
- `BaseEngine` subclass
|
||||
- `Template` class with `.render(context, request)`
|
||||
|
||||
Everything Django expects from a template backend, but the actual
|
||||
rendering routes to Bun.
|
||||
Reference in New Issue
Block a user