AFI parity: close all 35 gaps — every adapter wires every AFI-common capability
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>
This commit is contained in:
@@ -26,6 +26,15 @@ pub struct FunctionArgs {
|
||||
pub merge: Vec<Path>,
|
||||
pub websocket: bool,
|
||||
pub private: bool,
|
||||
/// `auth = "required" | "staff" | "superuser"` (or bare `auth` ⇒
|
||||
/// "required") — the `@client(auth=...)` guard. Bare-true and the string
|
||||
/// `"required"` both mean "must be authenticated".
|
||||
pub auth: Option<String>,
|
||||
/// `form_name = "..."` + `form_role = "schema"|"validate"|"submit"` — the
|
||||
/// Forms binding's per-endpoint metadata, mirroring the Django form
|
||||
/// `_meta` keys. Carried into the IR (`is-form`/`form-name`/`form-role`).
|
||||
pub form_name: Option<String>,
|
||||
pub form_role: Option<String>,
|
||||
}
|
||||
|
||||
impl FunctionArgs {
|
||||
@@ -45,10 +54,16 @@ impl FunctionArgs {
|
||||
out.affects = collect_paths(&nv.value)?;
|
||||
} else if nv.path.is_ident("merge") {
|
||||
out.merge = collect_paths(&nv.value)?;
|
||||
} else if nv.path.is_ident("auth") {
|
||||
out.auth = Some(expect_str(&nv.value)?);
|
||||
} else if nv.path.is_ident("form_name") {
|
||||
out.form_name = Some(expect_str(&nv.value)?);
|
||||
} else if nv.path.is_ident("form_role") {
|
||||
out.form_role = Some(expect_str(&nv.value)?);
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.path,
|
||||
"unknown attribute key; expected one of: context, affects, merge",
|
||||
"unknown attribute key; expected one of: context, affects, merge, auth, form_name, form_role",
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -57,10 +72,12 @@ impl FunctionArgs {
|
||||
out.websocket = true;
|
||||
} else if p.is_ident("private") {
|
||||
out.private = true;
|
||||
} else if p.is_ident("auth") {
|
||||
out.auth = Some("required".to_string());
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
p,
|
||||
"unknown flag; expected `websocket` or `private`",
|
||||
"unknown flag; expected `websocket`, `private`, or `auth`",
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -99,6 +116,21 @@ fn expect_path(expr: &Expr) -> syn::Result<Path> {
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_str(expr: &Expr) -> syn::Result<String> {
|
||||
if let Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(s),
|
||||
..
|
||||
}) = expr
|
||||
{
|
||||
Ok(s.value())
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
expr,
|
||||
"expected a string literal (e.g. `\"staff\"`)",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_paths(expr: &Expr) -> syn::Result<Vec<Path>> {
|
||||
match expr {
|
||||
Expr::Path(_) => Ok(vec![expect_path(expr)?]),
|
||||
@@ -183,7 +215,11 @@ pub fn expand(args: FunctionArgs, item: ItemFn) -> TokenStream {
|
||||
});
|
||||
}
|
||||
quote! {
|
||||
#[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
// The synthetic Input is only ever *deserialized* (from the call's
|
||||
// JSON args by the dispatch wrapper); it is never serialized, so it
|
||||
// derives `Deserialize` only. Dropping `Serialize` lets binary
|
||||
// field types like `Upload` (deserialize-only) participate.
|
||||
#[derive(::std::fmt::Debug, ::std::clone::Clone, ::serde::Deserialize)]
|
||||
pub struct #input_type_ident {
|
||||
#(#field_defs)*
|
||||
}
|
||||
@@ -353,6 +389,20 @@ pub fn expand(args: FunctionArgs, item: ItemFn) -> TokenStream {
|
||||
let output_nullable = analysis.nullable;
|
||||
let private = args.private;
|
||||
|
||||
let auth_value = match &args.auth {
|
||||
Some(a) => quote! { ::std::option::Option::Some(#a) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let is_form = args.form_name.is_some() || args.form_role.is_some();
|
||||
let form_name_value = match &args.form_name {
|
||||
Some(n) => quote! { ::std::option::Option::Some(#n) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let form_role_value = match &args.form_role {
|
||||
Some(r) => quote! { ::std::option::Option::Some(#r) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let dispatch_body = build_dispatch(
|
||||
&item,
|
||||
&input_args,
|
||||
@@ -389,6 +439,10 @@ pub fn expand(args: FunctionArgs, item: ItemFn) -> TokenStream {
|
||||
fn merge(&self) -> &'static [&'static str] { #merge_static }
|
||||
fn transport(&self) -> ::mizan_core::Transport { #transport_value }
|
||||
fn private(&self) -> bool { #private }
|
||||
fn auth(&self) -> ::std::option::Option<&'static str> { #auth_value }
|
||||
fn is_form(&self) -> bool { #is_form }
|
||||
fn form_name(&self) -> ::std::option::Option<&'static str> { #form_name_value }
|
||||
fn form_role(&self) -> ::std::option::Option<&'static str> { #form_role_value }
|
||||
fn input_params(&self) -> &'static [::mizan_core::InputParam] { #params_static }
|
||||
|
||||
fn dispatch<'a>(
|
||||
|
||||
Reference in New Issue
Block a user