返回 CodeWhale
check-runtime-contract-budget.py
根目录 / scripts / check-runtime-contract-budget.py
1 #!/usr/bin/env python3
2 """Enforce one-way ceilings on Codewhale's provider-free runtime contract.
3
4 The default invocation runs ``measure-runtime-contract.py`` with Cargo forced
5 offline. Pass ``--receipt`` to check an existing JSON receipt without compiling,
6 which also keeps this checker's unit tests hermetic.
7
8 Usage:
9 python3 scripts/check-runtime-contract-budget.py
10 python3 scripts/check-runtime-contract-budget.py --receipt receipt.json
11 python3 scripts/check-runtime-contract-budget.py --update
12 """
13
14 from __future__ import annotations
15
16 import argparse
17 import copy
18 import hashlib
19 import json
20 import os
21 import re
22 import shlex
23 import stat
24 import subprocess
25 import sys
26 import tempfile
27 from pathlib import Path
28 from typing import Any, Sequence
29
30 REPO_ROOT = Path(__file__).resolve().parent.parent
31 BUDGET_PATH = REPO_ROOT / "scripts" / "runtime-contract-budget.json"
32 MEASURE_SCRIPT = REPO_ROOT / "scripts" / "measure-runtime-contract.py"
33 RECEIPT_KIND = "codewhale.runtime_contract_receipt"
34 BUDGET_KIND = "codewhale.runtime_contract_budget"
35 SCHEMA_VERSION = 1
36 REPRESENTATIVE_FIXTURE_ID = "representative-v1"
37 TOOL_SURFACE_PROFILE = "production-default-builtins-no-mcp-no-host-interpreters-bash-v2"
38
39 MetricPath = tuple[str, ...]
40 MetricResult = tuple[str, str, int, int]
41
42 VISIBLE_MODES = (("plan", "Plan"), ("act", "Act"), ("operate", "Operate"))
43 REPRESENTATIVE_STAGES = (
44 ("base", "base"),
45 ("project", "project-authority"),
46 ("instructions", "configured-instructions"),
47 ("skill", "skill"),
48 ("memory", "memory"),
49 ("goal", "goal"),
50 ("handoff", "handoff"),
51 )
52 TOOL_SURFACES = (("full", "full"), ("active", "active"))
53
54 METRICS: tuple[tuple[MetricPath, str], ...] = (
55 *(
56 (("system_prompt", "modes", mode, field), f"{label} {description}")
57 for mode, label in VISIBLE_MODES
58 for field, description in (
59 ("system_prompt_bytes", "system-prompt bytes"),
60 ("system_prompt_tokens_est", "system-prompt estimated tokens"),
61 ("system_prompt_blocks", "system-prompt blocks"),
62 ("mode_instructions_bytes", "mode-instruction bytes"),
63 ("mode_instructions_tokens_est", "mode-instruction estimated tokens"),
64 )
65 ),
66 *(
67 (
68 ("representative_context", "stages", stage, "bytes"),
69 f"representative {label} stage bytes",
70 )
71 for stage, label in REPRESENTATIVE_STAGES
72 ),
73 *(
74 (
75 ("representative_context", "stages", stage, "delta_bytes"),
76 f"representative {label} stage delta bytes",
77 )
78 for stage, label in REPRESENTATIVE_STAGES[1:]
79 ),
80 (
81 ("representative_context", "total_bytes"),
82 "representative total bytes",
83 ),
84 (
85 ("representative_context", "total_tokens_est"),
86 "representative estimated tokens",
87 ),
88 (
89 ("representative_context", "system_prompt_blocks"),
90 "representative system-prompt blocks",
91 ),
92 *(
93 (
94 ("tool_catalog", "modes", mode, surface, field),
95 f"{label} {surface_label} {description}",
96 )
97 for mode, label in VISIBLE_MODES
98 for surface, surface_label in TOOL_SURFACES
99 for field, description in (
100 ("tools", "tool count"),
101 ("bytes", "tool-schema bytes"),
102 ("tokens_est", "tool-schema estimated tokens"),
103 )
104 ),
105 (
106 ("skill_discovery", "first_delta", "root_discovery_calls"),
107 "first unchanged-turn root discovery calls",
108 ),
109 (
110 ("skill_discovery", "first_delta", "directories_visited"),
111 "first unchanged-turn directories visited",
112 ),
113 (
114 ("skill_discovery", "first_delta", "skill_md_read_attempts"),
115 "first unchanged-turn SKILL.md read attempts",
116 ),
117 (
118 ("skill_discovery", "second_delta", "root_discovery_calls"),
119 "second unchanged-turn root discovery calls",
120 ),
121 (
122 ("skill_discovery", "second_delta", "directories_visited"),
123 "second unchanged-turn directories visited",
124 ),
125 (
126 ("skill_discovery", "second_delta", "skill_md_read_attempts"),
127 "second unchanged-turn SKILL.md read attempts",
128 ),
129 )
130
131 IDENTITIES: tuple[tuple[MetricPath, str], ...] = (
132 (("tool_catalog", "surface_profile"), "tool surface profile"),
133 (("tool_catalog", "execution_shell"), "tool fixture shell"),
134 *(
135 (
136 ("tool_catalog", "modes", mode, surface, field),
137 f"{label} {surface_label} tool {description}",
138 )
139 for mode, label in VISIBLE_MODES
140 for surface, surface_label in TOOL_SURFACES
141 for field, description in (
142 ("tool_names", "names"),
143 ("identity_sha256", "identity digest"),
144 )
145 ),
146 *(
147 (
148 ("representative_context", "stages", stage, "identity_sha256"),
149 f"representative {label} stage identity digest",
150 )
151 for stage, label in REPRESENTATIVE_STAGES
152 ),
153 )
154
155
156 class RuntimeContractError(ValueError):
157 """A receipt or budget is missing a required, well-typed metric."""
158
159
160 def load_json(path: Path, kind: str) -> dict[str, Any]:
161 try:
162 document = json.loads(path.read_text(encoding="utf-8"))
163 except FileNotFoundError as error:
164 raise RuntimeContractError(f"missing {kind}: {path}") from error
165 except (OSError, json.JSONDecodeError) as error:
166 raise RuntimeContractError(f"invalid {kind} {path}: {error}") from error
167 if not isinstance(document, dict):
168 raise RuntimeContractError(f"invalid {kind} {path}: top level must be an object")
169 return document
170
171
172 def validate_document(
173 document: dict[str, Any], expected_kind: str, source: str
174 ) -> None:
175 actual_kind = document.get("document_kind")
176 if actual_kind != expected_kind:
177 raise RuntimeContractError(
178 f"{source} document_kind must be `{expected_kind}`, got {actual_kind!r}"
179 )
180 version = document.get("schema_version")
181 if (
182 isinstance(version, bool)
183 or not isinstance(version, int)
184 or version != SCHEMA_VERSION
185 ):
186 raise RuntimeContractError(
187 f"{source} schema_version must be {SCHEMA_VERSION}, got {version!r}"
188 )
189
190
191 def required_value(document: dict[str, Any], path: MetricPath, kind: str) -> Any:
192 value: Any = document
193 dotted = ".".join(path)
194 for part in path:
195 if not isinstance(value, dict) or part not in value:
196 raise RuntimeContractError(f"{kind} is missing required field `{dotted}`")
197 value = value[part]
198 return value
199
200
201 def tool_identity_digest(names: list[str]) -> str:
202 return hashlib.sha256("\0".join(names).encode("utf-8")).hexdigest()
203
204
205 def validate_identity_structure(document: dict[str, Any], kind: str) -> None:
206 profile = required_value(document, ("tool_catalog", "surface_profile"), kind)
207 if profile != TOOL_SURFACE_PROFILE:
208 raise RuntimeContractError(
209 f"{kind} tool surface_profile must be `{TOOL_SURFACE_PROFILE}`, "
210 f"got {profile!r}"
211 )
212
213 shell = required_value(document, ("tool_catalog", "execution_shell"), kind)
214 if shell != "bash":
215 raise RuntimeContractError(
216 f"{kind} tool execution_shell must be `bash`, got {shell!r}"
217 )
218
219 for mode, _label in VISIBLE_MODES:
220 for surface, _surface_label in TOOL_SURFACES:
221 base = ("tool_catalog", "modes", mode, surface)
222 names = required_value(document, (*base, "tool_names"), kind)
223 dotted_names = ".".join((*base, "tool_names"))
224 if (
225 not isinstance(names, list)
226 or any(not isinstance(name, str) or not name for name in names)
227 or names != sorted(set(names))
228 ):
229 raise RuntimeContractError(
230 f"{kind} field `{dotted_names}` must be sorted unique non-empty strings"
231 )
232 count = metric_value(document, (*base, "tools"), kind)
233 if count != len(names):
234 raise RuntimeContractError(
235 f"{kind} metric `{'.'.join((*base, 'tools'))}` must equal the "
236 f"owned tool_names length ({len(names)})"
237 )
238 digest = required_value(document, (*base, "identity_sha256"), kind)
239 expected = tool_identity_digest(names)
240 if digest != expected:
241 raise RuntimeContractError(
242 f"{kind} field `{'.'.join((*base, 'identity_sha256'))}` must "
243 "match the owned sorted tool_names"
244 )
245
246 for stage, _label in REPRESENTATIVE_STAGES:
247 path = ("representative_context", "stages", stage, "identity_sha256")
248 digest = required_value(document, path, kind)
249 if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
250 raise RuntimeContractError(
251 f"{kind} field `{'.'.join(path)}` must be a lowercase SHA-256 digest"
252 )
253
254
255 def validate_receipt(receipt: dict[str, Any]) -> None:
256 validate_document(receipt, RECEIPT_KIND, "receipt")
257 skill_discovery = receipt.get("skill_discovery")
258 identical = (
259 skill_discovery.get("prompts_byte_identical")
260 if isinstance(skill_discovery, dict)
261 else None
262 )
263 if identical is not True:
264 raise RuntimeContractError(
265 "receipt metric `skill_discovery.prompts_byte_identical` must be true"
266 )
267 representative = receipt.get("representative_context")
268 fixture_id = (
269 representative.get("fixture_id")
270 if isinstance(representative, dict)
271 else None
272 )
273 if fixture_id != REPRESENTATIVE_FIXTURE_ID:
274 raise RuntimeContractError(
275 "receipt metric `representative_context.fixture_id` must be "
276 f"`{REPRESENTATIVE_FIXTURE_ID}`, got {fixture_id!r}"
277 )
278 representative_identical = representative.get("prompts_byte_identical")
279 if representative_identical is not True:
280 raise RuntimeContractError(
281 "receipt metric `representative_context.prompts_byte_identical` must be true"
282 )
283 validate_identity_structure(receipt, "receipt")
284
285
286 def validate_budget(budget: dict[str, Any]) -> None:
287 validate_document(budget, BUDGET_KIND, "budget")
288 representative = budget.get("representative_context")
289 fixture_id = (
290 representative.get("fixture_id")
291 if isinstance(representative, dict)
292 else None
293 )
294 if fixture_id != REPRESENTATIVE_FIXTURE_ID:
295 raise RuntimeContractError(
296 "budget metric `representative_context.fixture_id` must be "
297 f"`{REPRESENTATIVE_FIXTURE_ID}`, got {fixture_id!r}"
298 )
299 validate_identity_structure(budget, "budget")
300
301
302 def metric_value(document: dict[str, Any], path: MetricPath, kind: str) -> int:
303 value = required_value(document, path, kind)
304 dotted = ".".join(path)
305 if isinstance(value, bool) or not isinstance(value, int) or value < 0:
306 raise RuntimeContractError(
307 f"{kind} metric `{dotted}` must be a non-negative integer"
308 )
309 return value
310
311
312 def compare(
313 receipt: dict[str, Any], budget: dict[str, Any]
314 ) -> tuple[list[MetricResult], list[MetricResult]]:
315 """Return (increases, decreases) as path/label/current/ceiling tuples."""
316 validate_receipt(receipt)
317 validate_budget(budget)
318 for path, label in IDENTITIES:
319 receipt_value = required_value(receipt, path, "receipt")
320 budget_value = required_value(budget, path, "budget")
321 if receipt_value != budget_value:
322 detail = ""
323 if isinstance(receipt_value, list) and isinstance(budget_value, list):
324 added = [str(item) for item in receipt_value if item not in budget_value]
325 removed = [
326 str(item) for item in budget_value if item not in receipt_value
327 ]
328 detail = f" (added={added} removed={removed})"
329 raise RuntimeContractError(
330 f"identity changed for {label} [`{'.'.join(path)}`]{detail}"
331 )
332 increases: list[MetricResult] = []
333 decreases: list[MetricResult] = []
334 for path, label in METRICS:
335 current = metric_value(receipt, path, "receipt")
336 ceiling = metric_value(budget, path, "budget")
337 result = (".".join(path), label, current, ceiling)
338 if current > ceiling:
339 increases.append(result)
340 elif current < ceiling:
341 decreases.append(result)
342 return increases, decreases
343
344
345 def set_path_value(document: dict[str, Any], path: MetricPath, value: Any) -> None:
346 target = document
347 for part in path[:-1]:
348 target = target.setdefault(part, {})
349 target[path[-1]] = value
350
351
352 def budget_from_receipt(receipt: dict[str, Any]) -> dict[str, Any]:
353 validate_receipt(receipt)
354 budget: dict[str, Any] = {
355 "_comment": (
356 "One-way numeric ceilings and exact structural identities for the "
357 "provider-free runtime contract. Decreases pass; increases or identity "
358 "changes fail. Lock in decreases with: python3 "
359 "scripts/check-runtime-contract-budget.py --update"
360 ),
361 "document_kind": BUDGET_KIND,
362 "schema_version": SCHEMA_VERSION,
363 "representative_context": {
364 "fixture_id": REPRESENTATIVE_FIXTURE_ID,
365 },
366 }
367 for path, _label in METRICS:
368 set_path_value(budget, path, metric_value(receipt, path, "receipt"))
369 for path, _label in IDENTITIES:
370 set_path_value(
371 budget,
372 path,
373 copy.deepcopy(required_value(receipt, path, "receipt")),
374 )
375 return budget
376
377
378 def write_budget_atomic(path: Path, budget: dict[str, Any]) -> None:
379 """Replace an existing budget atomically without changing its mode bits."""
380 original_mode = stat.S_IMODE(path.stat().st_mode)
381 payload = json.dumps(budget, indent=2, sort_keys=True) + "\n"
382 file_descriptor, temporary_name = tempfile.mkstemp(
383 prefix=f".{path.name}.", suffix=".tmp", dir=path.parent
384 )
385 temporary_path = Path(temporary_name)
386 try:
387 with os.fdopen(file_descriptor, "w", encoding="utf-8") as handle:
388 handle.write(payload)
389 handle.flush()
390 os.fsync(handle.fileno())
391 os.chmod(temporary_path, original_mode)
392 os.replace(temporary_path, path)
393 except BaseException:
394 temporary_path.unlink(missing_ok=True)
395 raise
396
397
398 def run_measurement() -> dict[str, Any]:
399 env = os.environ.copy()
400 env["CARGO_NET_OFFLINE"] = "true"
401 proc = subprocess.run(
402 [sys.executable, str(MEASURE_SCRIPT)],
403 cwd=REPO_ROOT,
404 env=env,
405 capture_output=True,
406 text=True,
407 check=False,
408 )
409 sys.stderr.write(proc.stderr)
410 if proc.returncode != 0:
411 sys.stdout.write(proc.stdout)
412 raise RuntimeContractError(
413 f"runtime-contract measurement failed with exit code {proc.returncode}"
414 )
415 try:
416 receipt = json.loads(proc.stdout)
417 except json.JSONDecodeError as error:
418 raise RuntimeContractError(f"measurement emitted invalid JSON: {error}") from error
419 if not isinstance(receipt, dict):
420 raise RuntimeContractError("measurement top level must be an object")
421 validate_receipt(receipt)
422 return receipt
423
424
425 def update_command(receipt_path: Path | None, budget_path: Path) -> str:
426 parts = ["python3", "scripts/check-runtime-contract-budget.py"]
427 if receipt_path is not None:
428 parts.extend(["--receipt", str(receipt_path)])
429 if budget_path != BUDGET_PATH:
430 parts.extend(["--budget", str(budget_path)])
431 parts.append("--update")
432 return shlex.join(parts)
433
434
435 FRAGMENT_MODULE = REPO_ROOT / "crates" / "core" / "src" / "fragments.rs"
436 FRAGMENT_MAX_TOKENS_CEILING = 10_000
437 FRAGMENT_MAX_BYTES_CEILING = FRAGMENT_MAX_TOKENS_CEILING * 4
438 FRAGMENT_DEFAULT_MAX_BYTES_CEILING = 4 * 1024
439 FRAGMENT_MAX_COUNT_CEILING = 16
440
441
442 def check_fragment_caps() -> None:
443 """Gate the bounded fragment hard caps (issue #5264).
444
445 Static check — no cargo needed. Fails closed if the fragment module is
446 missing, if any cap has been raised without review, or if the
447 project-instruction import is absent.
448 """
449 try:
450 text = FRAGMENT_MODULE.read_text(encoding="utf-8")
451 except FileNotFoundError as error:
452 raise RuntimeContractError(
453 f"missing bounded fragment module: {FRAGMENT_MODULE} ({error})"
454 ) from error
455
456 def const_value(pattern: str) -> int:
457 match = re.search(pattern, text)
458 if not match:
459 raise RuntimeContractError(f"fragment cap missing: {pattern}")
460 try:
461 return int(match.group(1).replace("_", ""))
462 except ValueError as error:
463 raise RuntimeContractError(f"fragment cap not an int: {pattern}") from error
464
465 max_tokens = const_value(r"pub const MAX_FRAGMENT_TOKENS:\s*usize\s*=\s*([0-9_]+)")
466 if max_tokens != FRAGMENT_MAX_TOKENS_CEILING:
467 raise RuntimeContractError(
468 f"MAX_FRAGMENT_TOKENS must be {FRAGMENT_MAX_TOKENS_CEILING}, got {max_tokens}"
469 )
470 # MAX_FRAGMENT_BYTES must be defined as MAX_FRAGMENT_TOKENS * 4 (canonical)
471 # or as a literal 40000. Either way the derived ceiling is 40_000.
472 has_multiplication = re.search(
473 r"pub const MAX_FRAGMENT_BYTES:\s*usize\s*=\s*MAX_FRAGMENT_TOKENS\s*\*\s*4", text
474 )
475 bytes_literal = re.search(
476 r"pub const MAX_FRAGMENT_BYTES:\s*usize\s*=\s*([0-9_]+)", text
477 )
478 if bytes_literal:
479 literal = int(bytes_literal.group(1).replace("_", ""))
480 if literal != FRAGMENT_MAX_BYTES_CEILING:
481 raise RuntimeContractError(
482 f"MAX_FRAGMENT_BYTES must be {FRAGMENT_MAX_BYTES_CEILING}, got {literal}"
483 )
484 elif not has_multiplication:
485 raise RuntimeContractError(
486 "MAX_FRAGMENT_BYTES must be defined as MAX_FRAGMENT_TOKENS * 4 or as 40000"
487 )
488 # DEFAULT is defined as 4 * 1024 (canonical) or 4096 literal
489 has_default_multiplication = re.search(
490 r"pub const DEFAULT_FRAGMENT_MAX_BYTES:\s*usize\s*=\s*4\s*\*\s*1024", text
491 )
492 default_literal = re.search(
493 r"pub const DEFAULT_FRAGMENT_MAX_BYTES:\s*usize\s*=\s*([0-9_]+)", text
494 )
495 if has_default_multiplication:
496 # canonical 4*1024 == 4096, which equals ceiling
497 pass
498 elif default_literal:
499 default_bytes = int(default_literal.group(1).replace("_", ""))
500 if default_bytes != FRAGMENT_DEFAULT_MAX_BYTES_CEILING:
501 raise RuntimeContractError(
502 f"DEFAULT_FRAGMENT_MAX_BYTES must be {FRAGMENT_DEFAULT_MAX_BYTES_CEILING}, got {default_bytes}"
503 )
504 if default_bytes > FRAGMENT_MAX_BYTES_CEILING:
505 raise RuntimeContractError(
506 f"DEFAULT_FRAGMENT_MAX_BYTES ({default_bytes}) must not exceed MAX_FRAGMENT_BYTES ({FRAGMENT_MAX_BYTES_CEILING})"
507 )
508 else:
509 raise RuntimeContractError("DEFAULT_FRAGMENT_MAX_BYTES definition not found")
510
511 max_count = const_value(
512 r"pub const MAX_FRAGMENTS_PER_CONTEXT:\s*usize\s*=\s*([0-9_]+)"
513 )
514 if max_count != FRAGMENT_MAX_COUNT_CEILING:
515 raise RuntimeContractError(
516 f"MAX_FRAGMENTS_PER_CONTEXT must be {FRAGMENT_MAX_COUNT_CEILING}, got {max_count}"
517 )
518 if max_count > FRAGMENT_MAX_COUNT_CEILING:
519 raise RuntimeContractError(
520 f"MAX_FRAGMENTS_PER_CONTEXT ({max_count}) must not exceed {FRAGMENT_MAX_COUNT_CEILING}"
521 )
522
523 # Ensure every injection type is in FragmentId::all() and the
524 # project-instruction import is present as a typed fragment.
525 required_fragments = [
526 "Workspace",
527 "Permissions",
528 "Route",
529 "AgentTopology",
530 "SkillsTools",
531 "TokenBudget",
532 "ProjectInstructions",
533 "Constitution",
534 ]
535 for name in required_fragments:
536 if f"Self::{name}" not in text and f"{name} =>" not in text and f'"{name.lower()}"' not in text.lower():
537 # Fallback: search for enum variant declaration
538 if not re.search(rf"\b{name}\b", text):
539 raise RuntimeContractError(
540 f"FragmentId missing required variant {name}"
541 )
542 # Marker stability — these strings are pinned by tests / prefix cache
543 required_markers = [
544 "<!-- cw:ctx:workspace -->",
545 "<!-- cw:ctx:project_instructions -->",
546 "<!-- cw:ctx:constitution -->",
547 ]
548 for marker in required_markers:
549 if marker not in text:
550 raise RuntimeContractError(
551 f"bounded fragment module missing required marker {marker!r}"
552 )
553
554 # Project-instruction import must be a typed fragment, not ad-hoc
555 if "load_project_instruction_fragment" not in text:
556 raise RuntimeContractError(
557 "bounded fragment module must expose load_project_instruction_fragment (project-instruction import as typed fragment)"
558 )
559 if "PROJECT_INSTRUCTION_CANDIDATES" not in text:
560 raise RuntimeContractError(
561 "bounded fragment module must define PROJECT_INSTRUCTION_CANDIDATES"
562 )
563 # Required candidate files from #3978
564 required_candidates = [
565 ".cursorrules",
566 ".clinerules",
567 ".windsurf/rules",
568 ".gemini",
569 ".github/copilot-instructions.md",
570 ]
571 for candidate in required_candidates:
572 if candidate not in text:
573 raise RuntimeContractError(
574 f"PROJECT_INSTRUCTION_CANDIDATES missing required entry {candidate!r}"
575 )
576
577 # matches_text recognizer must exist on the fragment trait
578 if "fn matches_text" not in text:
579 raise RuntimeContractError(
580 "bounded fragment module must define a matches_text recognizer on the fragment trait"
581 )
582 if "trait ContextFragment" not in text:
583 raise RuntimeContractError(
584 "bounded fragment module must define trait ContextFragment with matches_text"
585 )
586
587 # No unbounded fragment — enforce that creation clamps to MAX_FRAGMENT_BYTES
588 if "MAX_FRAGMENT_BYTES" not in text or "enforce_byte_cap" not in text:
589 raise RuntimeContractError(
590 "bounded fragment module must enforce byte caps via enforce_byte_cap and MAX_FRAGMENT_BYTES"
591 )
592
593 # TUI must be unified with the core boundary (shared crates/core module)
594 tui_fragment = REPO_ROOT / "crates" / "tui" / "src" / "model_context" / "fragment.rs"
595 try:
596 tui_text = tui_fragment.read_text(encoding="utf-8")
597 except FileNotFoundError as error:
598 raise RuntimeContractError(
599 f"missing TUI fragment module: {tui_fragment} ({error})"
600 ) from error
601 if "codewhale_core::fragments" not in tui_text:
602 raise RuntimeContractError(
603 "TUI model_context/fragment.rs must re-export caps from codewhale_core::fragments (shared crates/core boundary)"
604 )
605 if "ProjectInstructions" not in tui_text:
606 raise RuntimeContractError(
607 "TUI fragment module must include ProjectInstructions variant (unified with core)"
608 )
609 if "MAX_FRAGMENT_BYTES" not in tui_text:
610 raise RuntimeContractError(
611 "TUI fragment module must enforce MAX_FRAGMENT_BYTES (10K-token ceiling)"
612 )
613 if "matches_text" not in tui_text:
614 raise RuntimeContractError(
615 "TUI fragment module must expose a matches_text recognizer"
616 )
617
618
619 def main(argv: Sequence[str] | None = None) -> int:
620 parser = argparse.ArgumentParser(description=__doc__)
621 parser.add_argument(
622 "--receipt",
623 type=Path,
624 help="check an existing measurement JSON instead of compiling",
625 )
626 parser.add_argument(
627 "--budget",
628 type=Path,
629 default=BUDGET_PATH,
630 help=argparse.SUPPRESS,
631 )
632 parser.add_argument(
633 "--update",
634 action="store_true",
635 help="tighten all ceilings to the current receipt; refuses increases",
636 )
637 args = parser.parse_args(argv)
638
639 try:
640 check_fragment_caps()
641 if args.receipt is not None and args.receipt.resolve() == args.budget.resolve():
642 raise RuntimeContractError(
643 "receipt and budget must resolve to distinct filesystem paths"
644 )
645 budget = load_json(args.budget, "budget")
646 receipt = (
647 load_json(args.receipt, "receipt")
648 if args.receipt is not None
649 else run_measurement()
650 )
651 increases, decreases = compare(receipt, budget)
652 except RuntimeContractError as error:
653 print(f"[runtime-contract-budget] ERROR: {error}", file=sys.stderr)
654 return 2
655
656 if increases:
657 print("[runtime-contract-budget] FAIL: runtime contract grew:", file=sys.stderr)
658 for path, label, current, ceiling in increases:
659 print(
660 f" {label}: {current} > {ceiling} (+{current - ceiling}) [{path}]",
661 file=sys.stderr,
662 )
663 print(
664 "\nReduce the model-facing surface or make any higher ceiling an explicit "
665 "maintainer decision in scripts/runtime-contract-budget.json.",
666 file=sys.stderr,
667 )
668 return 1
669
670 if args.update:
671 try:
672 write_budget_atomic(args.budget, budget_from_receipt(receipt))
673 except OSError as error:
674 print(
675 f"[runtime-contract-budget] ERROR: failed to update budget: {error}",
676 file=sys.stderr,
677 )
678 return 2
679 print(
680 f"[runtime-contract-budget] wrote {args.budget}: "
681 f"{len(decreases)} decreased ceilings locked in "
682 f"({len(METRICS)} total)"
683 )
684 return 0
685
686 if decreases:
687 print(
688 f"[runtime-contract-budget] PASS: {len(METRICS)} ceilings respected; "
689 f"{len(decreases)} can be tightened."
690 )
691 for path, label, current, ceiling in decreases:
692 print(f" {label}: {current} < {ceiling} (-{ceiling - current}) [{path}]")
693 print(f"Tighten with:\n {update_command(args.receipt, args.budget)}")
694 return 0
695
696 print(
697 f"[runtime-contract-budget] PASS: all {len(METRICS)} metrics are exactly at budget."
698 )
699 return 0
700
701
702 if __name__ == "__main__":
703 raise SystemExit(main())
704
704 lines PYTHON