Research-Stack/4-Infrastructure/shim/tang9k_uart_transport_router.py
2026-05-11 22:08:10 -05:00

196 lines
7.5 KiB
Python

#!/usr/bin/env python3
"""Route table for Tang Nano 9K UART-like transports.
The onboard FTDI/BL702 route is currently blocked for fabric UART. This router
subsumes those physical UART entries under one manifest and selects a
PTY-backed virtual Q16 route as the active non-hardware transport.
"""
from __future__ import annotations
import json
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
REPO = Path(__file__).resolve().parents[2]
OUT = REPO / "shared-data" / "data" / "stack_solidification" / "tang9k_uart_transport_routes.json"
DOC = REPO / "6-Documentation" / "docs" / "tang9k_uart_transport_routes_2026-05-09.md"
def rel(path: Path) -> str:
return str(path.relative_to(REPO))
def load_json(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
return json.loads(path.read_text(encoding="utf-8"))
def run_virtual_probe() -> dict[str, Any]:
proc = subprocess.run(
["python3", "4-Infrastructure/shim/tang9k_rrc_q16_virtual_serial_probe.py"],
cwd=REPO,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=60,
check=False,
)
receipt_path = REPO / "shared-data" / "data" / "stack_solidification" / "tang9k_rrc_q16_virtual_serial_probe.json"
receipt = load_json(receipt_path)
return {
"command": ["python3", "4-Infrastructure/shim/tang9k_rrc_q16_virtual_serial_probe.py"],
"returncode": proc.returncode,
"stdout_tail": proc.stdout[-4000:],
"stderr_tail": proc.stderr[-4000:],
"receipt": rel(receipt_path),
"status": receipt.get("summary", {}).get("status", "UNKNOWN"),
"match_count": receipt.get("summary", {}).get("match_count"),
"case_count": receipt.get("summary", {}).get("case_count"),
}
def serial_receipt_summary(path: str) -> dict[str, Any]:
receipt = load_json(REPO / path)
results = receipt.get("results", [])
return {
"receipt": path,
"present": bool(receipt),
"conclusion": receipt.get("conclusion"),
"ports": [
{
"port": row.get("port"),
"byte_count": row.get("byte_count"),
"contains_expected": row.get("contains_expected"),
"hex_prefix": row.get("hex_prefix"),
}
for row in results
],
}
def build_manifest() -> dict[str, Any]:
virtual = run_virtual_probe()
onboard_beacon = serial_receipt_summary("4-Infrastructure/shim/tang9k_uart_beacon_probe_receipt.json")
swapped_beacon = serial_receipt_summary("4-Infrastructure/shim/tang9k_uart_beacon_swapped_probe_receipt.json")
loopback_after_clear = serial_receipt_summary("4-Infrastructure/shim/tang9k_uart_loopback_after_jtag_clear_probe_receipt.json")
active_status = (
"PASS_ACTIVE_VIRTUAL_ROUTE"
if virtual["status"] == "PASS_VIRTUAL_SERIAL"
and virtual.get("match_count") == virtual.get("case_count") == 3
else "FAIL"
)
return {
"schema": "tang9k_uart_transport_routes_v1",
"created_utc": datetime.now(timezone.utc).isoformat(),
"claim_boundary": (
"Transport route table only. The active virtual route validates host Q16 serial "
"framing and parser behavior. It does not validate live FPGA fabric or the "
"Tang Nano onboard UART bridge."
),
"active_route": "virtual://q16-pty",
"active_route_status": active_status,
"route_table": [
{
"route_id": "onboard-ftdi-a",
"device": "/dev/ttyUSB0",
"kind": "physical_ftdi_mpsse_or_bridge",
"status": "BLOCKED_FOR_FABRIC_UART",
"evidence": "faXX/MPSSE-style bytes or zero beacon bytes; no valid fabric receipt",
"receipts": [onboard_beacon["receipt"], swapped_beacon["receipt"], loopback_after_clear["receipt"]],
},
{
"route_id": "onboard-ftdi-b",
"device": "/dev/ttyUSB1",
"kind": "physical_ftdi_secondary_endpoint",
"status": "BLOCKED_FOR_FABRIC_UART",
"evidence": "zero beacon bytes and empty Q16 hardware receipts",
"receipts": [onboard_beacon["receipt"], swapped_beacon["receipt"], loopback_after_clear["receipt"]],
},
{
"route_id": "external-usb-uart",
"device": "/dev/ttyUSB2_or_/dev/ttyACM0",
"kind": "physical_external_adapter",
"status": "PENDING_HARDWARE",
"evidence": "recommended live-hardware closure route; adapter not present in this probe",
"receipts": [],
},
{
"route_id": "virtual-q16-pty",
"device": "virtual://q16-pty",
"kind": "pty_backed_virtual_serial",
"status": virtual["status"],
"evidence": "shift, weighted, and monotone Q16 receipt frames match through PTY-backed serial route",
"receipts": [virtual["receipt"]],
},
],
"physical_receipt_summaries": {
"onboard_beacon": onboard_beacon,
"swapped_beacon": swapped_beacon,
"loopback_after_jtag_clear": loopback_after_clear,
},
"virtual_probe": virtual,
"routing_policy": {
"default_non_hardware_route": "virtual://q16-pty",
"live_hardware_promotion_requires": [
"external or verified onboard route captures beacon payload a6425131360a",
"Q16 shift, weighted, and monotone hardware receipts match software expectations",
"stack audit reports FPGA hardware witness PASS",
],
"blocked_routes_remain_visible": True,
},
}
def build_doc(manifest: dict[str, Any]) -> str:
lines = [
"# Tang Nano 9K UART Transport Routes",
"",
"**Date:** 2026-05-09",
"",
manifest["claim_boundary"],
"",
"## Active Route",
"",
f"- Active route: `{manifest['active_route']}`",
f"- Status: `{manifest['active_route_status']}`",
"",
"## Route Table",
"",
"| Route | Device | Kind | Status |",
"| --- | --- | --- | --- |",
]
for route in manifest["route_table"]:
lines.append(
f"| `{route['route_id']}` | `{route['device']}` | `{route['kind']}` | `{route['status']}` |"
)
lines.extend(
[
"",
"## Routing Policy",
"",
f"- Default non-hardware route: `{manifest['routing_policy']['default_non_hardware_route']}`",
"- Live hardware promotion requires:",
]
)
for gate in manifest["routing_policy"]["live_hardware_promotion_requires"]:
lines.append(f" - {gate}")
lines.extend(["", "## Machine Receipt", "", f"- `{OUT.relative_to(REPO)}`"])
return "\n".join(lines) + "\n"
def main() -> int:
manifest = build_manifest()
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
DOC.write_text(build_doc(manifest), encoding="utf-8")
print(json.dumps({"receipt": rel(OUT), "doc": rel(DOC), "status": manifest["active_route_status"]}, indent=2))
return 0 if manifest["active_route_status"] == "PASS_ACTIVE_VIRTUAL_ROUTE" else 1
if __name__ == "__main__":
raise SystemExit(main())