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:
159
backends/mizan-ts/tests/ir.test.ts
Normal file
159
backends/mizan-ts/tests/ir.test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* KDL IR byte-parity — the mizan-ts `buildIr()` against the canonical Python
|
||||
* `build_ir()` (`cores/mizan-python/src/mizan_core/ir.py`).
|
||||
*
|
||||
* The IR is the codegen contract. A TypeScript backend can only feed
|
||||
* `protocol/mizan-codegen` if it emits the same KDL the Python/Rust backends
|
||||
* emit for the same registry. This test reconstructs the AFI fixture in both
|
||||
* languages, subprocesses the live Python emitter, and asserts byte-equality —
|
||||
* the same discipline `protocol/mizan-codegen/tests/python_parity.rs` applies.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeEach } from 'bun:test'
|
||||
import { execFileSync } from 'child_process'
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { buildIr, clearRegistry } from '../src'
|
||||
import { registerFixture } from './ir-fixture'
|
||||
|
||||
const REPO_ROOT = resolve(import.meta.dir, '../../..')
|
||||
const MIZAN_PYTHON = resolve(REPO_ROOT, 'cores/mizan-python')
|
||||
|
||||
/**
|
||||
* Reconstruct the AFI fixture in Python via `mizan_core` only (no backend
|
||||
* adapter dependency) and emit `build_ir()`. This is the cross-language oracle:
|
||||
* the same registrations the TS fixture makes, run through the reference
|
||||
* emitter.
|
||||
*/
|
||||
const PY_FIXTURE = String.raw`
|
||||
import sys
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from mizan_core.client.function import client
|
||||
from mizan_core import registry as reg
|
||||
from mizan_core.ir import build_ir
|
||||
|
||||
reg.clear_registry()
|
||||
|
||||
class EchoOutput(BaseModel):
|
||||
message: str
|
||||
|
||||
class WhoamiOutput(BaseModel):
|
||||
email: str
|
||||
authenticated: bool
|
||||
|
||||
class ProfileOutput(BaseModel):
|
||||
user_id: int
|
||||
name: str
|
||||
|
||||
class OrderOutput(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
total: int
|
||||
|
||||
class StatusOutput(BaseModel):
|
||||
ok: bool
|
||||
|
||||
@client
|
||||
def echo(request, text: str) -> EchoOutput: ...
|
||||
|
||||
@client
|
||||
def whoami(request) -> WhoamiOutput: ...
|
||||
|
||||
@client(context="user")
|
||||
def user_profile(request, user_id: int) -> ProfileOutput: ...
|
||||
|
||||
@client(context="user")
|
||||
def user_orders(request, user_id: int) -> list[OrderOutput]: ...
|
||||
|
||||
@client(affects="user")
|
||||
def update_profile(request, user_id: int, name: str) -> StatusOutput: ...
|
||||
|
||||
@client
|
||||
def find_user(request, user_id: int) -> Optional[ProfileOutput]: ...
|
||||
|
||||
@client(merge="user")
|
||||
def rename_user(request, user_id: int, name: str) -> ProfileOutput: ...
|
||||
|
||||
for f in [echo, whoami, user_profile, user_orders, update_profile, find_user, rename_user]:
|
||||
reg.register(f, f.__name__)
|
||||
|
||||
sys.stdout.write(build_ir())
|
||||
`
|
||||
|
||||
function pythonBuildIr(): string {
|
||||
return execFileSync(
|
||||
'uv',
|
||||
['run', '--project', MIZAN_PYTHON, 'python', '-c', PY_FIXTURE],
|
||||
{ encoding: 'utf-8' },
|
||||
)
|
||||
}
|
||||
|
||||
const UV_AVAILABLE = (() => {
|
||||
try {
|
||||
execFileSync('uv', ['--version'], { stdio: 'ignore' })
|
||||
return existsSync(resolve(MIZAN_PYTHON, 'pyproject.toml'))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})()
|
||||
|
||||
describe('KDL IR — buildIr()', () => {
|
||||
beforeEach(() => clearRegistry())
|
||||
|
||||
test('emits the canonical type / function / context sections', () => {
|
||||
registerFixture()
|
||||
const kdl = buildIr()
|
||||
|
||||
// Types are alphabetical; output structs renamed to <camel>Output.
|
||||
expect(kdl).toContain('type "OrderOutput" {')
|
||||
expect(kdl).toContain('type "echoInput" {')
|
||||
expect(kdl).toContain('type "findUserOutput" {')
|
||||
expect(kdl).toContain('type "userOrdersOutput" {')
|
||||
|
||||
// Functions alphabetical, with transport + context/affects/merge leaves.
|
||||
expect(kdl).toContain('function "echo" {')
|
||||
expect(kdl).toContain(' camel "echo"')
|
||||
expect(kdl).toContain(' has-input #true')
|
||||
expect(kdl).toContain(' output-nullable #true') // find_user
|
||||
expect(kdl).toContain(' affects "user"') // update_profile
|
||||
expect(kdl).toContain(' merge "user"') // rename_user
|
||||
|
||||
// Context section with shared param elevation.
|
||||
expect(kdl).toContain('context "user" {')
|
||||
expect(kdl).toContain(' shared-by "user_orders"')
|
||||
expect(kdl).toContain(' shared-by "user_profile"')
|
||||
})
|
||||
|
||||
test('has-input #false for a no-arg function', () => {
|
||||
registerFixture()
|
||||
const kdl = buildIr()
|
||||
const whoami = kdl.slice(kdl.indexOf('function "whoami" {'))
|
||||
expect(whoami).toContain('has-input #false')
|
||||
expect(whoami).not.toContain('input "whoamiInput"')
|
||||
})
|
||||
|
||||
test.skipIf(!UV_AVAILABLE)(
|
||||
'byte-identical to the Python build_ir() (cores/mizan-python)',
|
||||
() => {
|
||||
registerFixture()
|
||||
const tsKdl = buildIr()
|
||||
const pyKdl = pythonBuildIr()
|
||||
|
||||
// Line-by-line first so a divergence names the offending line.
|
||||
const tsLines = tsKdl.split('\n')
|
||||
const pyLines = pyKdl.split('\n')
|
||||
const n = Math.max(tsLines.length, pyLines.length)
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (tsLines[i] !== pyLines[i]) {
|
||||
throw new Error(
|
||||
`KDL diverges at line ${i + 1}:\n` +
|
||||
` python: ${JSON.stringify(pyLines[i])}\n` +
|
||||
` ts: ${JSON.stringify(tsLines[i])}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
expect(tsKdl).toBe(pyKdl)
|
||||
},
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user