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:
2026-06-04 13:44:35 -04:00
parent 58d2cb2848
commit 6c5f6f1fba
81 changed files with 9893 additions and 463 deletions

View File

@@ -0,0 +1,120 @@
//! Cross-language pin: Rust `derive_cache_key` must be byte-identical to the
//! Python reference (`cores/mizan-python/.../cache/keys.py`) and to the
//! committed cross-language vectors that `tests/afi` and `mizan-ts` also pin.
//!
//! The Python reference is the oracle: a subprocess mints the key with fixed
//! inputs and the Rust output must match exactly. `never if backend == X` —
//! one spec, pinned both ways.
use mizan_core::derive_cache_key;
use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::Command;
/// The `tests/afi` dir, whose venv has `mizan_core` + PyJWT installed.
fn afi_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/afi")
.canonicalize()
.expect("tests/afi exists")
}
/// Run the Python reference via `uv run python -c <code>` in tests/afi and
/// return its single stdout line, trimmed.
fn py(code: &str) -> String {
let out = Command::new("uv")
.args(["run", "python", "-c", code])
.current_dir(afi_dir())
.output()
.expect("invoke uv run python");
assert!(
out.status.success(),
"python reference failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
String::from_utf8(out.stdout).unwrap().trim().to_string()
}
fn tree(pairs: &[(&str, Value)]) -> BTreeMap<String, Value> {
pairs.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()
}
#[test]
fn committed_vectors_match() {
// The exact pins committed in cores/mizan-python/tests/test_keys.py and
// backends/mizan-ts/tests — the canonical cross-language anchor.
let secret = "test-pin-secret-that-is-32bytes!";
let public = derive_cache_key(secret, "user", &tree(&[("user_id", json!("5"))]), None, 0);
assert_eq!(
public,
"ctx:user:605a1ca5ad5994e9b765c8d1b330474c2a0d51a7b8fbbdc402f992da7ba902f6"
);
let scoped = derive_cache_key(
secret,
"user",
&tree(&[("user_id", json!("5"))]),
Some("5"),
0,
);
assert_eq!(
scoped,
"ctx:user:30fc08eb46ee4ff2cf7d317e97dca90fd616511e0587304416f71dc863338dc2"
);
}
#[test]
fn matches_python_reference_across_inputs() {
// A spread of shapes: multi-param (order-independence), numeric vs string,
// bool/null normalization, user-scoped, nonzero rev.
let cases: Vec<(&str, BTreeMap<String, Value>, Option<&str>, i64)> = vec![
("user", tree(&[("user_id", json!("5"))]), None, 0),
("user", tree(&[("user_id", json!("5"))]), Some("5"), 0),
("user", tree(&[("user_id", json!("5"))]), Some("5"), 3),
(
"search",
tree(&[("q", json!("hello world")), ("page", json!(2))]),
None,
0,
),
(
"flags",
tree(&[("on", json!(true)), ("off", json!(false)), ("nil", json!(null))]),
Some("42"),
1,
),
("empty", tree(&[]), None, 0),
(
"unicode",
tree(&[("name", json!("café—ñ"))]),
None,
0,
),
];
for (ctx, params, uid, rev) in cases {
let rust = derive_cache_key("pin-secret-xyz", ctx, &params, uid, rev);
// Build the Python call: derive_cache_key(secret, ctx, params, user_id, rev).
let params_json = serde_json::to_string(
&params.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<serde_json::Map<_, _>>(),
)
.unwrap();
let uid_arg = match uid {
Some(u) => format!("'{u}'"),
None => "None".to_string(),
};
let code = format!(
"import json; from mizan_core.cache.keys import derive_cache_key; \
print(derive_cache_key('pin-secret-xyz', {ctx:?}, json.loads(r'''{params_json}'''), {uid_arg}, {rev}))",
);
let expected = py(&code);
assert_eq!(
rust, expected,
"cache-key mismatch for ctx={ctx} params={params_json} uid={uid:?} rev={rev}",
);
}
}