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>
91 lines
3.2 KiB
Rust
91 lines
3.2 KiB
Rust
//! Cross-language pin: Rust `format_invalidate_header` must be byte-identical
|
|
//! to `cores/mizan-python/.../invalidation.py::format_invalidate_header`.
|
|
//!
|
|
//! The `X-Mizan-Invalidate` header is co-equal with the JSON body channel in
|
|
//! the spec; Edge parses it to purge. The Python reference is the oracle.
|
|
|
|
use mizan_core::{format_invalidate_header, InvalidationTarget};
|
|
use serde_json::json;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
fn afi_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../tests/afi")
|
|
.canonicalize()
|
|
.expect("tests/afi exists")
|
|
}
|
|
|
|
fn py_header(json_list: &str) -> String {
|
|
let code = format!(
|
|
"import json; from mizan_core.invalidation import format_invalidate_header; \
|
|
print(format_invalidate_header(json.loads(r'''{json_list}''')))",
|
|
);
|
|
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: {}",
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
// Trim the trailing newline only — the header value itself may be empty.
|
|
let s = String::from_utf8(out.stdout).unwrap();
|
|
s.strip_suffix('\n').unwrap_or(&s).to_string()
|
|
}
|
|
|
|
fn scoped(ctx: &str, params: &[(&str, serde_json::Value)]) -> InvalidationTarget {
|
|
InvalidationTarget::ScopedContext {
|
|
context: ctx.to_string(),
|
|
params: params.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn matches_python_reference() {
|
|
let cases: Vec<(Vec<InvalidationTarget>, &str)> = vec![
|
|
(vec![InvalidationTarget::Context("user".into())], r#"["user"]"#),
|
|
(
|
|
vec![
|
|
InvalidationTarget::Context("user".into()),
|
|
InvalidationTarget::Context("notifications".into()),
|
|
],
|
|
r#"["user", "notifications"]"#,
|
|
),
|
|
(
|
|
vec![scoped("user", &[("user_id", json!(5))])],
|
|
r#"[{"context": "user", "params": {"user_id": 5}}]"#,
|
|
),
|
|
(
|
|
vec![scoped("search", &[("q", json!("hello world"))])],
|
|
r#"[{"context": "search", "params": {"q": "hello world"}}]"#,
|
|
),
|
|
(
|
|
// Multiple params → sorted by key, semicolon-joined.
|
|
vec![scoped("u", &[("b", json!("2")), ("a", json!("1"))])],
|
|
r#"[{"context": "u", "params": {"b": "2", "a": "1"}}]"#,
|
|
),
|
|
(
|
|
// Special chars that must percent-encode: &, =, /, space, unicode.
|
|
vec![scoped("c", &[("k", json!("a&b=c/d e—ñ"))])],
|
|
r#"[{"context": "c", "params": {"k": "a&b=c/d e—ñ"}}]"#,
|
|
),
|
|
(
|
|
// Mixed bare + scoped.
|
|
vec![
|
|
scoped("user", &[("user_id", json!(5))]),
|
|
InvalidationTarget::Context("notifications".into()),
|
|
],
|
|
r#"[{"context": "user", "params": {"user_id": 5}}, "notifications"]"#,
|
|
),
|
|
];
|
|
|
|
for (targets, json_list) in cases {
|
|
let rust = format_invalidate_header(&targets);
|
|
let expected = py_header(json_list);
|
|
assert_eq!(rust, expected, "header mismatch for {json_list}");
|
|
}
|
|
}
|