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>
The IR called them react-message and django-message, so a FastAPI channel had
to declare a DjangoMessage. They are client-message and server-message now,
and the direction words hold wherever a channel is declared: Params /
ClientMessage / ServerMessage, with mizan-core deriving <Pascal>Params and
friends so no backend names a type itself. Django's ReactChannel and
FastAPI's ReactChannel are both Channel.
mizan-fastapi never registered a channels extension, so build_ir() emitted no
channel at all and every payload type was invisible to codegen. It registers
one now. RegistryExtension is an ABC requiring all(), which is what the IR
reads — an extension that cannot enumerate its registrations no longer exists.
The gate that should have caught the rename could not: tests/afi registered no
channel because mizan-rust had no channel registry to register one in, so a
five-package rename of the wire contract passed byte-parity without a channel
byte crossing it. mizan-rust grows ChannelSlotKind, a CHANNELS slice, a
#[mizan::channel] macro, and KDL emission whose wire_to_pascal matches Python's
split; the AFI fixture now carries a channel with every slot and one with a
single slot, so all three backends prove the contract byte for byte.
MizanChannel held three Option<String> beside three has_*() predicates and
unwrapped them with defaults; it holds an ordered slot vector, so an absent
slot is absent rather than defaulted. The channels target emitted a React
hooks file that a stage1-only consumer could not compile — react emits that
now. The codegen's parity tests byte-compared emitted source against baselines
without ever compiling it: they compile the generated crate and run its tests,
import the generated Python package and call every method, and typecheck each
TypeScript target against a consumer.
Also fixed at source: app_visitor printed its import diagnostic to stdout, the
stream export_mizan_ir writes KDL to, so a failed import silently corrupted the
IR; the apps root was hardcoded to "apps"; _default_literal crashed build_ir on
any non-JSON-serializable field default; Django and mizan-core derived Pascal
names two different ways, disagreeing on every dotted channel name.
ir.py builds a document and renders templates/ir/document.kdl.j2 rather than
appending KDL strings with hand-tracked indentation, and named types resolve to
a fixed point — a model reachable only through a union branch was referenced by
a ref that no type block ever defined.
The rest is the write-gate's own classifiers run over the standing tree:
relative imports, silent swallows, Protocol contracts that should be ABCs,
emitters hand-rendering target source, catch-all arms over closed enums, and
comments narrating the project rather than the code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The socket carried rpc but not ctx, so a socket transport could not fetch a
context and every app needed an HTTP connection beside it. Dispatches through
the same execute_context the HTTP view calls; that view's origin-side cache is
a CDN concern with nothing in front of a socket, so it is not on this path.
Both backends now answer subscribe/unsubscribe/message/rpc/ctx on one envelope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mizan-django's socket sent a bare result, so a mutation called over it dropped
its invalidation while the same mutation over HTTP applied it. Both backends
now put the {result, invalidate, merge} envelope in `data` — the shape the
Tauri and webview transports already document — so mizanCall applies
server-driven invalidation identically whichever transport carried the call.
Two channel tests pinned the bare-result shape and move with the contract.
FastAPI gains a `ctx` action: without it a socket transport cannot fetch a
context bundle, and every app needs an HTTP connection beside the socket.
@mizan/ws-transport implements MizanTransport over one connection — RPC and
context bundles correlated by id, channel subscriptions re-sent on reconnect,
in-flight calls rejected when the socket closes under them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The auth-provider concern becomes a dedicated Django system. Removed from
mizan: the Django integration (mizan/integrations/allauth — auth contexts
+ ~15 form wrappers), the legacy/ pre-kernel TypeScript client, the
allauth and webauthn dependency extras (fido2 was consumed only by the
WebAuthn form wrappers), and the HEADLESS_JWT_* settings fallbacks — the
allauth-headless compat seam belongs to the dedicated system, not to
mizan's JWT module. Duplicate-name registration in discovery now surfaces
a warning instead of passing silently. README claims updated to point at
mizan-allauth; the root README's hand-maintained status matrix collapsed
into the tests/afi conformance suite as the parity authority.
OWED_SURFACE.md refreshed against the post-extraction tree (22 units).
mizan-django suite: 350 passed, 21 skipped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Roll the working tree back to the last approved shape, before the post-LICENSE span that false-greened the AFI parity matrix with symbol-presence probes and smuggled an unauthorized SQLAlchemy dependency into FastAPI's Shapes binding.
Forward commit, not a history rewrite — the six commits since 4effcc7 stay in the log as the record of what happened.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The conformance board (tests/afi/test_capability_parity.py) is now fully green:
90 capability cells + 4 meta-locks + 3 codegen byte-parity = 97 passed. The
gaps the prose table used to launder as "Django-only" / "out of scope" are
wired, against the pinned-spec model (single-authored spec, byte-identical
conformance across languages) — never per-language reimplementation.
FastAPI — edge_manifest + PSR (logic single-sourced in mizan_core.manifest),
WebSocket RPC (/ws/ through the shared dispatch), SSR (the framework-agnostic
SSRBridge relocated to mizan_core.ssr; Django rides it from there), Shapes
(SQLAlchemy projection, same declaration surface as django-readers), Forms
(Pydantic schema/validate/submit).
Rust (Axum + Tauri + cores/mizan-rust) — X-Mizan-Invalidate header, auth=
enforcement, origin HMAC cache, edge manifest + PSR, WebSocket handler / IPC
subscription channel, multipart upload, SSR bridge, Shapes, Forms; JWT/MWT
mint+verify and cache-key derivation byte-pinned to the Python reference
(cache_keys_pin, token_pin, invalidate_header_pin).
TypeScript — a KDL IR emitter byte-identical to the Python build_ir (so a TS
backend can feed the codegen — the largest gap), multipart upload, session-init,
WebSocket transport, SSR bridge, JWT/MWT mint (pinned to Python), Shapes, Forms.
Verified in the merged tree: core 25, fastapi 74, django 353/21-skip,
mizan-rust (incl. cross-language pins) green, axum 10, tauri 8, mizan-ts 103/2-skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds first-class Rust-backed Mizan to sit alongside mizan-django and
mizan-fastapi. A Rust dev writes:
#[derive(Mizan, Serialize, Deserialize)]
pub struct ProfileOutput { pub user_id: i64, pub name: String }
#[mizan::context("user")]
pub struct UserCtx;
#[mizan::client(context = UserCtx)]
pub async fn user_profile(_req: &RequestHandle<'_>, user_id: i64) -> ProfileOutput { ... }
…and gets byte-identical KDL to the Python emitters, served over the
same wire protocol the React / Rust / Vue / Svelte kernels speak.
New crates:
- cores/mizan-rust/ (Cargo: mizan-core) — IR types, KDL emitter, traits, registry,
runtime (compute_invalidation / compute_merges
ported from mizan-fastapi), graph_check with
structural type-matching
- cores/mizan-rust-macros/ (Cargo: mizan-macros) — #[derive(Mizan)], #[mizan::context],
#[mizan::client] proc macros
- backends/mizan-rust-axum/ (Cargo: mizan-axum) — axum HTTP adapter: /session/, /call/, /ctx/:name/
- tests/afi/rust_app/ — AFI fixture port + server / export-ir binaries
Substrate-shape moves required by cross-language equivalence:
- IR canonicalization: functions / contexts / context-members / shared-by
now sort alphabetically in both Python and Rust emitters. The IR is a
contract; linkme doesn't preserve declaration order, so canonical sort
is the only stable mapping. afi_ir.kdl + per-target baselines regenerated.
- MizanType::TYPE_NAME is a const (with a default type_name() reader) so
it's usable in linkme TypeEntry static initializers.
- Tree-shaken type registry: #[derive(Mizan)] only emits the trait impl;
the #[mizan::client] macro registers canonical-named entries from
fn signatures, including Vec<T> element types for ref resolution.
- Merge resolution is structural (NamedType shape comparison) rather than
by name — matches the Python types_match_for_merge semantics.
Three-way forcing functions:
- tests/afi/test_codegen_parity.py — Django ≡ FastAPI ≡ Rust on KDL bytes (3 pass)
- tests/rust/run_wire_parity.py — 12/12 probes against FastAPI + Rust (EXIT=0)
Incidental fixes surfaced by the new tests:
- Stale `from .registry import validate_registry` import removed from
mizan-django/setup/discovery.py (referenced a function that no longer
exists; was masking codegen-parity).
- BASE_DIR added to tests/afi/django_app/project/settings.py.
- /session/ endpoint added to mizan-fastapi for protocol-shaped readiness
probe parity (wire-parity harness now polls /api/mizan/session/ on both
backends rather than FastAPI's /openapi.json).
- Root .gitignore picks up Rust target/ across the tree so new crates
don't need per-crate gitignore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the transitional OpenAPI 3.0 + `x-mizan-*` extensions
substrate with the canonical Mizan IR as KDL, per docs/AFI_ARCHITECTURE.md:
"KDL is the contract; everything else (REST envelopes, OpenAPI
documents, framework idioms) is sediment around it."
End-to-end cutover. No transitional path left on main.
Forward direction:
cores/mizan-python/src/mizan_core/ir.py
build_ir() walks mizan_core.registry, introspects Pydantic
models directly (no JSON-Schema indirection), and emits the
Mizan IR document. The KDL grammar is locked in this file's
module docstring.
Backends emit KDL:
backends/mizan-fastapi/src/mizan_fastapi/ir.py
`python -m mizan_fastapi.ir <module>` — CLI entry point.
backends/mizan-django/.../management/commands/export_mizan_ir.py
`manage.py export_mizan_ir` — Django mgmt command.
Codegen consumes KDL:
protocol/mizan-codegen/Cargo.toml: + kdl = "6"
protocol/mizan-codegen/src/ir.rs: NamedType { Struct/List/Enum/Alias }
+ TypeShape { Primitive/Ref/List/Optional/Enum/Union } sum types,
replacing the JsonSchema sprawl. KDL parser walks the
`kdl::KdlDocument` tree into typed Rust structs.
protocol/mizan-codegen/src/fetch.rs: subprocess command switches
to the new IR-export entry points.
All emit modules (stage1 / react / python / rust / vue / svelte /
channels) port their type-walkers from JsonSchema to the new
sum types — case analysis collapses substantially.
Substrate-honesty wins beyond the moat closure:
- `int | bool` multi-arm unions land as `TypeShape::Union` (was
silently coerced to "string" before).
- `<CamelName>Output = list[T]` returns emit as named alias
types instead of struct-shaped wrappers, so consumer code
`.map()` works directly on the type.
- Pydantic field defaults flow through to `default` properties
in KDL, then back to non-optional shape in every target.
Deleted:
- backends/mizan-fastapi/src/mizan_fastapi/{cli,schema}.py
- backends/mizan-django/.../export_mizan_schema.py
- openapi-bearing half of mizan/export/__init__.py (edge
manifest generator preserved — separate concern).
- tests/afi/schema_normalizer.py
- tests/fixtures/{afi_schema.json, channels_schema.json}
- tests/fixtures/js_* baseline directories.
Verification:
- 20 mizan-codegen unit tests green (IR deserialization,
byte-equivalence parity across stage1/rust/python/react/vue/svelte
against fresh KDL-driven baselines, channels structural).
- tests/rust/run_wire_parity.py: 12/12 probes green driving
the binary end-to-end through KDL.
- Blazr studio-ui typechecks against the regenerated React client.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The @client(merge=[context, ...]) decorator lets a mutation patch its
return value directly into the cached context bundle by matching the
mutation's Output type against each context-function's Output type
to identify the slot, then splicing server-side. Kernel runs
splice_slot on the response to apply locally — no refetch, no
invalidate-cascade.
Lands H14, H15, H16, M19, M20 from ISSUES.md.
Backends (Django + FastAPI):
_resolve_merges() in both executors walks @client(merge=...) targets,
resolves the per-context slot via types_match_for_merge, and emits
{context, slot, value, params?} entries on the response. Param
auto-scoping mirrors _resolve_invalidation's tier-1 logic.
Frontend kernel (mizan-base):
Response handler reads the merge[] array and applies splice_slot
for each entry — locates the cached context bundle by name+params,
overwrites the named slot with the new value, notifies subscribers.
Core (mizan-python):
@client decorator extended with merge= parameter. Schema export
threads merge metadata onto the OpenAPI x-mizan-functions entries.
Examples / fixtures:
fastapi-react-site harness exercises merge + Playwright spec covers
the end-to-end happy path (mutation → instant UI update without
network refetch). AFI fixture's rename_user function is the
canonical merge target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The codegen consumes a schema from any backend and emits typed client
code for any frontend — it doesn't belong inside a backend adapter.
That placement was historical sediment from when there was only a
Django backend; it predates the AFI generalization.
New top-level slot: `protocol/` for protocol-level tooling. Tree is
now:
backends/ server protocol adapters
frontends/ client kernel + per-framework adapters
cores/ shared language-level primitives
protocol/ protocol-level tooling
workers/ runtime workers / bridges
Codegen moves to `protocol/mizan-generate/`. Same file layout under
`generator/` (cli.mjs, lib/), preserved via git mv.
Package metadata cleaned up:
- name: "generate" (placeholder) → "mizan-generate"
- description filled in
- type: module (cli.mjs is .mjs ESM, was previously declared "commonjs")
- bin entry added so `npx mizan-generate --config <config.mjs>` works
once the package is published, instead of `node path/to/cli.mjs`.
Path-reference fixups:
- backends/mizan-django/README.md: `node path/to/...` → `npx mizan-generate`
- backends/mizan-fastapi/README.md: same
- ISSUES.md: file paths in three issue entries
- CLAUDE.md: codegen description + Package Layout section refreshed
(added protocol/, mizan-fastapi entry, mizan-python entry)
- docs/AFI_ARCHITECTURE.md: Package Layout refreshed identically
Verified codegen runs from new location: regenerated the FastAPI
example harness's api/ output, identical to pre-move.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mizan-django/README.md:
- Updated install path (was pointing at the old `subdirectory=django` git
layout from before the backends/ restructure).
- Dropped the dead "monorepo root README" link (the root README was
removed earlier in the substrate-restoration work).
- Fixed the apps.py example — convention is `clients.py` per MIZAN.md,
not `mizan_clients.py`.
- Added the `mizan_clients()` auto-discovery pattern (it was missing).
- Added a Generate-the-frontend section: config shape + CLI invocation
+ the resulting <MizanContext>/use{Hook}() React surface.
- Tightened decorator-parameter overview to a single block covering the
full @client surface.
mizan-fastapi/README.md (new):
- Mirrors mizan-django's structure for consistency.
- Opens with the AFI-common scope: forms/channels/shapes/SSR are out of
scope on the FastAPI side; FastAPI projects use native equivalents.
- Setup shows app.add_exception_handler wiring for MizanError +
RequestValidationError so every error surface goes through the same
envelope the kernel parses.
- Calls out explicit register() (no AppConfig.ready() analog on FastAPI;
registrations live in main.py or an imported clients.py).
- Auth-integration section explains the request.state.user middleware
contract the executor expects.
- Codegen section shows the source.fastapi config shape that points at
the new `python -m mizan_fastapi.cli <module>` schema export.
- Closes with pointers to AFI conformance + the e2e harness so a reader
can verify the adapter's claims.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Demonstration milestone. The substrate work earlier in the session
established that mizan-fastapi can dispatch RPC, bundle context
fetches, and emit invalidation envelopes via TestClient (in-process
ASGI). This commit closes the demonstration gap: a real FastAPI server
on port 8001 + a real React harness on port 5175 + Playwright in real
Chromium, exercising generated hooks.
What ships:
backends/mizan-fastapi/src/mizan_fastapi/cli.py — schema-export CLI:
- `python -m mizan_fastapi.cli <module>` imports the named module
(triggering @client decorations + register() side effects), then
prints the OpenAPI schema to stdout. Mirrors mizan-django's
`manage.py export_mizan_schema` so the codegen consumes either
backend the same subprocess way.
backends/mizan-django/generate/generator/lib/fetch.mjs — codegen now
dispatches on source.django vs source.fastapi. Refactored the
subprocess plumbing into a shared runSubprocess helper. The codegen
package is still named "mizan-django" by historical accident — it's
the framework-agnostic CLI now (a rename for later).
backends/mizan-fastapi/src/mizan_fastapi/executor.py — bug fix:
mizan_core's @client decorator normalizes auth=True to
meta['auth']='required'. The executor's match was only handling True,
not 'required', so any auth-required endpoint failed with
INTERNAL_ERROR. Now matches both. Caught when wiring up the FastAPI
example backend's whoami fixture; would have surfaced first time any
real FastAPI app used auth=True.
backends/mizan-fastapi/tests/test_dispatch.py — added AuthTests
covering the auth=True path so the bug fix has unit coverage. Suite
now 12/12.
examples/fastapi-react-site/ — parallel to examples/django-react-site/:
- backend/main.py: FastAPI app with 11 @client fixtures matching the
harness surface (echo, add, multiply, whoami, staff/superuser/
verified-only, notImplementedFn, buggyFn, permissionCheckFn,
current_user context). Drops Django-only stuff (forms, channels,
ws-whoami, session-bound JWT).
- harness/: vite proxy → FastAPI on 8001; generated api/ produced by
the codegen against fastapi.config.mjs.
- mizan.spec.ts: Playwright suite, 14 tests covering the same axes
as Django minus channel-chat.
- ContextCurrentUser fixture renders 'loading' until data arrives
rather than emitting <pre>null</pre> — fixes a race the Django
harness has too (just doesn't trip in practice).
Verified:
- mizan-fastapi unit: 12/12 (incl. new auth=True coverage)
- mizan-fastapi e2e: 14/14 (Playwright via real Chromium)
- mizan-core unit: 15/15
- mizan-django unit: 348 pass, 21 skip
- AFI conformance: 3/3
- mizan-django e2e: 14/15 (1 skip — channels, deferred)
What remains for FastAPI side:
- Dockerfile.test + docker-compose.test.yml so CI can run the e2e
in the same containerized way as the Django example.
- Makefile test-integration target for symmetry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The harness was written against the MIZAN.md oracle (<MizanContext>,
provider-per-context, useMizan, etc.) but the codegen had been narrowed
to just hooks-direct-on-kernel after the kernel split. Restoring the
React-idiomatic layer on top of the kernel.
backends/mizan-django/generate/generator/lib/adapters/react.mjs:
- Emits <MizanContext baseUrl="…"> root provider that calls configure()
once and (if a global context is registered) wraps children in
<GlobalContextProvider>.
- Emits <GlobalContextProvider> + <{Name}Context> per named context —
kernel registration happens once per provider mount, not per hook
call. Consumers read from React Context.
- Base hooks: useGlobalContext() / use{Name}Context() return full
ContextState<T> (data + status + error).
- Convenience hooks per context-function (use{Fn}() returns data | null)
and per regular function/mutation (use{Fn}() returns
{ mutate, isPending, error }).
- useMizan() returns { call, fetch } as an imperative escape hatch
for test harnesses or rare cases where typed hooks don't fit.
- Re-exports MizanError, configure, initSession, ContextState from
@mizan/base.
backends/mizan-django/generate/generator/cli.mjs:
- After Stage 2, appends `export * from './<adapter>'` to index.ts so
`import { useEcho, MizanContext } from './api'` works as a barrel.
Bug fixes surfaced during integration:
- react.mjs was generating `from '../index'` (wrong path); flat layout
needs `./index`.
- harness django.config.mjs had `output: 'src/api/generated.ts'` which
the codegen treated as a directory; corrected to `output: 'src/api'`.
- example testapp/clients.py imported from the deleted
mizan.setup.registry path; routed through mizan.setup aggregator.
harness/package.json: adds @mizan/base dep so the generated react.tsx
can resolve its kernel imports.
harness/src/fixtures.tsx:
- DjangoError → MizanError (kernel error class, backend-agnostic).
- useChatChannel sourced from ./api/channels.hooks directly (not
re-exported from the unified index for now).
- Form fixtures removed — forms codegen deferred per Blazr scope.
Verified: harness `vite build` succeeds, 53 modules transformed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The @client decorator + ServerFunction base + composition machinery is
mostly framework-agnostic. The only Django couplings were typing
(HttpRequest in __init__ and submit_handler signatures) and runtime
view-path detection (HttpResponseBase isinstance/issubclass checks).
Replaced both with backend-extension hooks:
- HttpRequest type hints → Any. Type Protocol can be tightened later.
- HttpResponseBase view-path detection → set_framework_response_base(cls)
hook in mizan_core.client.function. Backends register their framework's
response base at import time. is_framework_response(obj_or_cls) handles
both instance and subclass checks via the registered base.
mizan-django registers HttpResponseBase via mizan/client/__init__.py
before any @client-decorated code is loaded. FastAPI would similarly
register starlette.responses.Response.
Direct consumers updated:
- mizan/setup/discovery.py: ServerFunction import path
- mizan/forms/__init__.py: ServerFunction + create_form_functions imports
mizan/client/__init__.py keeps its public re-export surface stable so
'from mizan.client import client, ServerFunction, …' continues to work
for downstream Django consumers.
Verified:
- mizan-core: 15/15
- mizan-django: 348 pass, 21 skip, 0 fail
- mizan-ts edge-compat: 34/34
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original registry tangled function, channel, composition, and form
registration in a single file with polymorphic register() dispatch.
That predates the household discipline; it was the design that was
supposed to ship but didn't. Re-implementing the original intent.
cores/mizan-python/src/mizan_core/registry.py (new):
- _functions, _compositions dicts
- register() — ServerFunction-only, no polymorphic dispatch
- register_as(), register_compose()
- register_extension(name, extension) — hook interface
- get_function/get_compose/get_all_functions/get_all_compositions
- get_contexts, get_context_groups
- get_registry, get_schema — aggregate extension contributions
- validate_registry, clear_registry — cascade-clear extensions
RegistryExtension Protocol:
- schema() returns the extension's schema subdict (keyed under its name)
- clear() resets extension state (called by clear_registry)
mizan-django/src/mizan/channels/__init__.py:
- _ChannelsExtension wraps the channel _registry, plugs into core via
register_extension('channels', ...). Schema output preserves the
same shape codegen consumed before (snake_case keys, type+bidirectional).
mizan-django/src/mizan/forms/__init__.py:
- register_form() and get_forms() helpers moved here (were in setup/registry.py)
- Both use mizan_core.registry under the hood. Forms don't need a
separate extension because form sub-functions register as regular
ServerFunctions with meta.form set.
mizan-django/src/mizan/setup/registry.py: deleted.
mizan-django/src/mizan/setup/__init__.py: re-exports the registry helpers
from mizan_core.registry / mizan.channels / mizan.forms — the Django
adapter's curated public API surface stays stable for users.
Consumers updated: ~10 files imported `from mizan.setup.registry`;
all switched to direct imports from mizan_core.registry, mizan.channels,
or mizan.forms as appropriate. ChannelTests in test_core.py rewritten
to use mizan.channels.register directly (no more polymorphic
@register_as on ReactChannel subclasses).
Verified:
- mizan-core: 15/15
- mizan-django: 348 pass, 21 skip, 0 fail
- mizan-ts edge-compat: 34/34 (cross-language pin holds)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cache/backend.py is pure framework-agnostic key-value abstraction —
CacheBackend Protocol, MemoryCache, RedisCache. No Django imports.
Moves to cores/mizan-python/src/mizan_core/cache/backend.py with no
content changes; mizan-django re-imports.
Verified: mizan-core 15/15, mizan-django 348 pass / 21 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull cache/keys.py (HMAC cache key derivation) and mwt.py (Mizan Web Token)
out of backends/mizan-django and into a new cores/mizan-python package.
mizan-django re-imports them via the new mizan_core module.
Naming: directory cores/mizan-python/, distribution mizan-core, importable
module mizan_core. mizan-django keeps its existing 'mizan' distribution slot
on PyPI; the two coexist as distinct packages.
Wiring:
- backends/mizan-django/pyproject.toml gains a 'mizan-core' dep with a
[tool.uv.sources] path entry (editable install from ../../cores/mizan-python).
- Makefile install target prepends 'cd cores/mizan-python && uv pip install -e .'
- 3 import sites in mizan-django updated: cache/__init__.py, jwt/functions.py,
client/executor.py — all now import from mizan_core.
Test split:
- 3 unit-test classes (CacheKeyDerivationTests, MWTCreationTests,
PermissionKeyTests) move to cores/mizan-python/tests/, rewritten against
unittest.TestCase (no Django dep). The cross-language pin test (pinned
HMAC hex digests against mizan-ts) moves with CacheKeyDerivationTests.
- Integration tests stay in mizan-django (CacheBackendTests, CachePurgeTests,
CacheIntegrationTests, RevParameterTests, MWTAuthIntegrationTests) — they
need the Django request flow.
Verified:
- mizan-core: 15/15 pass (incl. cross-language pin)
- mizan-django: 348 pass, 21 skip, 0 fail
- mizan-ts: edge-compat 34/34 pass — protocol invariant holds, the moved
Python derive_cache_key still produces the exact hex digests TS pins against.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fe39fcb commit captured the file moves (git mv stages those automatically)
but didn't catch the content edits I made afterward — npm package rename
(@mizan/runtime → @mizan/base), path updates in Makefile/Dockerfile/examples,
and doc updates were all left unstaged at commit time.
This commit lands those:
- npm rename: 3 frontend package.jsons (base/vue/svelte) + mizan-base/src/index.ts + 4 codegen templates
- path updates: Makefile, Dockerfile.test, two Gitea workflows, four example/harness configs
- doc updates: CLAUDE.md, ROADMAP.md, ISSUES.md, docs/AFI_ARCHITECTURE.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
packages/ flattens into:
backends/ server protocol adapters (mizan-django, mizan-ts)
frontends/ client kernel + framework adapters (mizan-base, mizan-react, mizan-vue, mizan-svelte)
workers/ runtime workers (mizan-ssr)
cores/ shared language-level primitives (empty for now; mizan-python forthcoming)
The frontend kernel (was packages/mizan-runtime, now frontends/mizan-base) is
renamed to reflect its role — it's the shared base that frontend adapters
depend on directly. Reflects the substrate position that per-framework adapters
wrap a single shared kernel; codegen targets the adapter, not the raw kernel.
Path updates landed in: Makefile, two Gitea workflows, Dockerfile.test, four
example/harness config files, .claude/settings.local.json, four docs
(CLAUDE/ISSUES/ROADMAP/AFI_ARCHITECTURE), four codegen templates (stage1 +
react/vue/svelte adapters), and three package.jsons (the mizan-base rename
plus mizan-vue/svelte peerDeps).
Generated files under examples/django-react-site/harness/src/api/ still
reference @mizan/runtime — left as-is; they're regenerated artifacts and
the harness is non-functional pending the React wrapper-layer codegen.
Also folded in a pre-existing fix: the Gitea workflows had
working-directory: react / django pointing at a layout that predates
packages/, never updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>