""" The AFI surface, as data — the single source of truth for what every Mizan adapter owes. This module exists because "what is AFI-common" used to live as prose: a README table, a fixture docstring, an adapter `__init__` comment. Prose drifts. An adapter that didn't wire a capability got its gap relabelled "Django-only" or "out of scope — use native equivalents," and nothing went red. Here the surface is a list of `Capability` objects and the implementors are a list of `Adapter` objects; the parity table and the conformance suite are both *generated* from these two lists. A capability cannot be de-scoped by editing a word — only by deleting it from `CAPABILITIES`, which is a reviewable diff, not a buried cell. Applicability ("—" in the table) is DERIVED, never asserted: a capability that `requires={"transport": "http"}` is simply not applicable to an IPC adapter. The header-invalidation channel does not exist over Tauri IPC because IPC has no headers — that is a fact about the transport, computed by `applies()`, not a parity decision an agent gets to make. The line between AFI-common and genuinely-backend-bound: AFI-common Every adapter owes a binding. The protocol core, plus every `register_extension` point (WebSocket, SSR, JWT, MWT, Shapes, Forms). A missing one is a GAP (❌), never a category. Backend-bound Not in this manifest at all. `allauth` is a Django-ecosystem package — legitimately Django-only. The *bindings* of common capabilities are backend-specific (django-readers is Django's Shapes binding; Django Forms is Django's Forms binding) — but the *capability* is common, so it lives here and each adapter owes its own binding, not an "N/A." """ from __future__ import annotations from dataclasses import dataclass, field from enum import Enum class Tier(str, Enum): """How the README groups capabilities — presentation only, not semantics.""" PROTOCOL_CORE = "Protocol core" EDGE_CACHE = "Edge, cache & enforcement" EXTENSION = "Extension points" @dataclass(frozen=True) class Capability: """One thing every applicable adapter owes a binding for. `requires` declares preconditions an adapter must meet for this capability to APPLY. The only key today is `transport` ("http" | "ipc"); a capability that names one is inapplicable to adapters on the other, and `applies()` renders that as a derived "—". An empty `requires` means transport-agnostic: every adapter owes it, full stop. """ id: str title: str tier: Tier requires: dict[str, str] = field(default_factory=dict) note: str = "" @dataclass(frozen=True) class Adapter: """A backend adapter and the declared properties parity is computed against. `transport` is the load-bearing property: "http" adapters have a header channel, an edge, a session endpoint; "ipc" adapters (Tauri) do not, and those capabilities derive to "—" rather than counting as gaps. """ id: str title: str language: str # "python" | "rust" | "typescript" transport: str # "http" | "ipc" # ─── The AFI-common surface ─────────────────────────────────────────────────── CAPABILITIES: list[Capability] = [ # Protocol core — the wire contract every adapter implements. Capability("rpc_call", "RPC call dispatch (`{result, invalidate}`)", Tier.PROTOCOL_CORE), Capability("context_bundle", "Named-context bundle fetch", Tier.PROTOCOL_CORE), Capability("invalidate_body", "Invalidation — JSON body", Tier.PROTOCOL_CORE), Capability( "invalidate_header", "Invalidation — `X-Mizan-Invalidate` header", Tier.PROTOCOL_CORE, requires={"transport": "http"}, note="The header channel is co-equal with the body channel in the spec. " "IPC transports carry invalidation in the response envelope instead.", ), Capability("invalidate_autoscope", "Invalidation auto-scoping (three-tier)", Tier.PROTOCOL_CORE), Capability("registration", "Function discovery / registration", Tier.PROTOCOL_CORE), Capability("ir_export", "Codegen IR export (KDL)", Tier.PROTOCOL_CORE), Capability("upload", "File uploads (`Upload` type)", Tier.PROTOCOL_CORE), # Edge, cache & enforcement. Capability("auth_enforcement", "Auth-guard enforcement (`auth=…` rejects)", Tier.EDGE_CACHE), Capability("origin_cache", "Origin-side HMAC cache", Tier.EDGE_CACHE), Capability( "edge_manifest", "Edge manifest export", Tier.EDGE_CACHE, requires={"transport": "http"}, note="The manifest configures an HTTP/CDN edge; a desktop IPC shell has no edge.", ), Capability( "psr", "PSR (`render_strategy` in manifest)", Tier.EDGE_CACHE, requires={"transport": "http"}, ), Capability( "session_init", "Session / CSRF init endpoint", Tier.EDGE_CACHE, requires={"transport": "http"}, ), # Extension points — the `register_extension` surface. AFI-common, every one. Capability("websocket", "WebSocket transport (`websocket=` declared)", Tier.EXTENSION), Capability("ssr_bridge", "SSR bridge (subprocess renderer)", Tier.EXTENSION), Capability("jwt", "JWT auth (access / refresh)", Tier.EXTENSION), Capability( "mwt", "MWT (edge identity token)", Tier.EXTENSION, requires={"transport": "http"}, note="MWT exists to key an edge cache; without an edge there is nothing to key.", ), Capability( "shapes", "Typed query projection (Shapes)", Tier.EXTENSION, note="The capability is AFI-common; the binding is per-ORM " "(django-readers on Django, the project's ORM elsewhere).", ), Capability( "forms", "Forms (schema / validate / submit)", Tier.EXTENSION, note="The capability is AFI-common; the binding is per-framework " "(Django Forms on Django, Pydantic-or-equivalent elsewhere).", ), ] # ─── The implementors ───────────────────────────────────────────────────────── ADAPTERS: list[Adapter] = [ Adapter("django", "Django", "python", "http"), Adapter("fastapi", "FastAPI", "python", "http"), Adapter("rust_axum", "Rust / Axum", "rust", "http"), Adapter("tauri", "Tauri", "rust", "ipc"), Adapter("typescript", "TypeScript", "typescript", "http"), ] CAPABILITIES_BY_ID: dict[str, Capability] = {c.id: c for c in CAPABILITIES} ADAPTERS_BY_ID: dict[str, Adapter] = {a.id: a for a in ADAPTERS} # Every capability owes exactly one probe in probes.py. The meta-conformance # test pins this so a capability can't be added without its gate. PROBES_REQUIRED: int = len(CAPABILITIES) def applies(capability: Capability, adapter: Adapter) -> bool: """Whether `capability` is applicable to `adapter`, from declared properties. This is the ONLY source of "—" in the parity table. A `False` here is a transport fact (IPC has no HTTP header channel), not a parity verdict. """ required_transport = capability.requires.get("transport") if required_transport is not None and adapter.transport != required_transport: return False return True