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:
153
cores/mizan-rust/tests/token_pin.rs
Normal file
153
cores/mizan-rust/tests/token_pin.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
//! Cross-language pin: Rust HS256 JWT + MWT must be byte-identical to the
|
||||
//! Python core (`auth/jwt.py`, `mwt.py`, both PyJWT-backed).
|
||||
//!
|
||||
//! Byte-identity is the whole point — Edge and the origin cache key on these
|
||||
//! tokens, so a one-byte divergence is a cache-key spoof surface. The Python
|
||||
//! reference is the oracle: it mints with fixed claims + a fixed `iat`/`exp`
|
||||
//! (we pin `now` on both sides) and the Rust token must match exactly. We also
|
||||
//! prove round-trip: Rust decodes a Python-minted token and vice-versa.
|
||||
|
||||
use mizan_core::{
|
||||
create_access_token, create_mwt, create_refresh_token, decode_jwt, decode_mwt,
|
||||
compute_permission_key, JwtConfig,
|
||||
};
|
||||
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(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()
|
||||
}
|
||||
|
||||
const NOW: i64 = 1_700_000_000;
|
||||
|
||||
#[test]
|
||||
fn jwt_access_token_matches_python() {
|
||||
let cfg = JwtConfig::new("jwt-pin-secret");
|
||||
let rust = create_access_token(&cfg, "42", "sess-abc", true, false, NOW);
|
||||
|
||||
// Python: freeze time to NOW, mint an access token with the same claims.
|
||||
let code = format!(
|
||||
"import time; from unittest import mock; \
|
||||
from mizan_core.auth.jwt import JWTConfig, create_access_token; \
|
||||
cfg = JWTConfig(private_key='jwt-pin-secret', public_key='jwt-pin-secret'); \
|
||||
orig = time.time; \
|
||||
time.time = lambda: {NOW}; \
|
||||
print(create_access_token('42', 'sess-abc', cfg, is_staff=True, is_superuser=False)); \
|
||||
time.time = orig",
|
||||
);
|
||||
let expected = py(&code);
|
||||
assert_eq!(rust, expected, "JWT access-token byte mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jwt_refresh_token_matches_python() {
|
||||
let cfg = JwtConfig::new("jwt-pin-secret");
|
||||
let rust = create_refresh_token(&cfg, "7", "sid-9", false, true, NOW);
|
||||
let code = format!(
|
||||
"import time; from mizan_core.auth.jwt import JWTConfig, create_refresh_token; \
|
||||
cfg = JWTConfig(private_key='jwt-pin-secret', public_key='jwt-pin-secret'); \
|
||||
time.time = lambda: {NOW}; \
|
||||
print(create_refresh_token('7', 'sid-9', cfg, is_staff=False, is_superuser=True))",
|
||||
);
|
||||
assert_eq!(rust, py(&code), "JWT refresh-token byte mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jwt_roundtrip_decode_python_minted() {
|
||||
// A Python-minted access token must decode in Rust with matching claims.
|
||||
let code = format!(
|
||||
"import time; from mizan_core.auth.jwt import JWTConfig, create_access_token; \
|
||||
cfg = JWTConfig(private_key='rt-secret', public_key='rt-secret'); \
|
||||
time.time = lambda: {NOW}; \
|
||||
print(create_access_token('99', 'sess-x', cfg, is_staff=False, is_superuser=True))",
|
||||
);
|
||||
let token = py(&code);
|
||||
let cfg = JwtConfig::new("rt-secret");
|
||||
let payload = decode_jwt(&token, &cfg, Some("access"), NOW + 10).expect("decodes");
|
||||
assert_eq!(payload.sub, "99");
|
||||
assert_eq!(payload.sid, "sess-x");
|
||||
assert!(payload.superuser);
|
||||
assert!(!payload.staff);
|
||||
// Wrong secret → None; expired → None.
|
||||
assert!(decode_jwt(&token, &JwtConfig::new("nope"), None, NOW + 10).is_none());
|
||||
assert!(decode_jwt(&token, &cfg, Some("access"), NOW + 10_000).is_none());
|
||||
// Type mismatch → None.
|
||||
assert!(decode_jwt(&token, &cfg, Some("refresh"), NOW + 10).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_key_matches_python() {
|
||||
let perms = vec!["app.add_thing".to_string(), "app.view_thing".to_string()];
|
||||
let rust = compute_permission_key(true, false, &perms);
|
||||
let code =
|
||||
"from mizan_core.mwt import compute_permission_key; \
|
||||
from unittest.mock import MagicMock; \
|
||||
u = MagicMock(); u.is_staff=True; u.is_superuser=False; \
|
||||
u.get_all_permissions = MagicMock(return_value={'app.view_thing','app.add_thing'}); \
|
||||
print(compute_permission_key(u))";
|
||||
assert_eq!(rust, py(code), "pkey byte mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mwt_matches_python() {
|
||||
// Build the same pkey on both sides, then mint with frozen time + fixed
|
||||
// kid/audience and compare bytes.
|
||||
let perms = vec!["app.view_thing".to_string()];
|
||||
let pkey = compute_permission_key(false, false, &perms);
|
||||
let rust = create_mwt("mwt-pin-secret", "5", false, false, &pkey, 300, "mizan", "v1", NOW);
|
||||
|
||||
let code = format!(
|
||||
"import time; from unittest.mock import MagicMock; \
|
||||
from mizan_core.mwt import create_mwt; \
|
||||
u = MagicMock(); u.pk=5; u.is_staff=False; u.is_superuser=False; \
|
||||
u.get_all_permissions = MagicMock(return_value={{'app.view_thing'}}); \
|
||||
time.time = lambda: {NOW}; \
|
||||
print(create_mwt(u, 'mwt-pin-secret', ttl=300, audience='mizan', kid='v1'))",
|
||||
);
|
||||
assert_eq!(rust, py(&code), "MWT byte mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mwt_roundtrip_and_rejections() {
|
||||
let pkey = compute_permission_key(true, true, &[]);
|
||||
let token = create_mwt("rt-mwt", "13", true, true, &pkey, 300, "mizan", "v1", NOW);
|
||||
let p = decode_mwt(&token, "rt-mwt", "mizan", NOW + 5).expect("decodes");
|
||||
assert_eq!(p.sub, "13");
|
||||
assert!(p.staff && p.superuser);
|
||||
assert_eq!(p.kid, "v1");
|
||||
assert_eq!(p.pkey.len(), 64);
|
||||
// Wrong secret, wrong audience, expired → None.
|
||||
assert!(decode_mwt(&token, "wrong", "mizan", NOW + 5).is_none());
|
||||
assert!(decode_mwt(&token, "rt-mwt", "other", NOW + 5).is_none());
|
||||
assert!(decode_mwt(&token, "rt-mwt", "mizan", NOW + 10_000).is_none());
|
||||
|
||||
// And a Python-minted MWT decodes in Rust.
|
||||
let code = format!(
|
||||
"import time; from unittest.mock import MagicMock; from mizan_core.mwt import create_mwt; \
|
||||
u = MagicMock(); u.pk=21; u.is_staff=True; u.is_superuser=False; \
|
||||
u.get_all_permissions = MagicMock(return_value=set()); \
|
||||
time.time = lambda: {NOW}; print(create_mwt(u, 'rt-mwt', ttl=300, audience='mizan', kid='v1'))",
|
||||
);
|
||||
let py_token = py(&code);
|
||||
let pp = decode_mwt(&py_token, "rt-mwt", "mizan", NOW + 5).expect("py mwt decodes in rust");
|
||||
assert_eq!(pp.sub, "21");
|
||||
assert!(pp.staff && !pp.superuser);
|
||||
}
|
||||
Reference in New Issue
Block a user