AtlatestRepositoryfolio

folio / tree / toolsf0_contract_gate.py

1#!/usr/bin/env python3
2"""Verify the frozen F0 entry-door contract still names real source surfaces."""
3
4import json
5import re
6import sys
7from pathlib import Path
8
9
10ROOT = Path(__file__).resolve().parent.parent
13def main() -> int:
14 contract = json.loads((ROOT / "docs/f0/api-entry-doors.json").read_text())
15 source_files = list((ROOT / "src/folio").glob("*.sgl"))
16 source = "\n".join(path.read_text() for path in source_files)
17 tools_source = (ROOT / "src/folio/tools.sgl").read_text()
18 actual_tools = sorted(set(re.findall(r'"(folio/[^"\s]+)"', tools_source)))
19 frozen_tools = sorted((ROOT / "docs/f0/mcp-tools.txt").read_text().splitlines())
20 failures = []
21 if actual_tools != frozen_tools:
22 failures.append(f"MCP tool set drift: actual={actual_tools!r} frozen={frozen_tools!r}")
23 names = []
24 names.extend(contract["library_query_entry_doors"])
25 names.extend(contract["index_entry_doors"])
26 for values in contract["mutation_entry_doors"].values():
27 if isinstance(values, list):
28 names.extend(values)
29 for entry in names:
30 name = entry.split(":")[-1]
31 if name not in source:
32 failures.append(f"contract entry door absent from source: {entry}")
33 if failures:
34 for failure in failures:
35 print("FAIL: " + failure, file=sys.stderr)
36 return 1
37 print(f"PASS: {len(frozen_tools)} MCP tools and {len(names)} library entry doors frozen")
38 return 0
41if __name__ == "__main__":
42 raise SystemExit(main())