|
| 1 | +"""Rebuild the symbol map the frozen corpus is scored against, and stage it where the scorer looks. |
| 2 | +
|
| 3 | +`benchmark/corpus/symbol_map.yaml` pins that map by a digest and records in prose how to rebuild |
| 4 | +it: `build_symbol_map` over a Stripe specification staged in `.cache/specs/`, with no SDK |
| 5 | +document. Prose is not a step a runner can execute, and the cost was exact -- CI fetched the |
| 6 | +corpus, reached `scripts/score_corpus.py`, and was refused with `no symbol map at |
| 7 | +.cache/specs/symbols.json`. The binding gate has never run on a runner, and the fetch failure |
| 8 | +that came before it hid that for as long as it lasted. |
| 9 | +
|
| 10 | +The SDK document is deliberately not read |
| 11 | +----------------------------------------- |
| 12 | +`_prepare_stripe` passes one when the tag publishes it, and the pin was taken without one. Both |
| 13 | +produce 272 symbols and the same digest, which the pin's own note records as measured. Passing |
| 14 | +the argument anyway would make this script's output depend on whether a large optional file |
| 15 | +happened to be staged, which is the class of thing the pin exists to stop. |
| 16 | +
|
| 17 | +Any pinned tag will do, and that is a property rather than a convenience |
| 18 | +----------------------------------------------------------------------- |
| 19 | +Measured across `v2200`, `v2300`, `v2330` and `v2345`: one digest, 272 symbols. The map is keyed |
| 20 | +by SDK symbol and derived from path segments and `x-stableId`, both of which are stable across |
| 21 | +these versions. `--spec` defaults to the head the corpus pairs are cut against; a caller naming |
| 22 | +another is checked by the same pin, so a version that ever did move the map fails here rather |
| 23 | +than scoring quietly. |
| 24 | +""" |
| 25 | + |
| 26 | +from __future__ import annotations |
| 27 | + |
| 28 | +import argparse |
| 29 | +import json |
| 30 | +import sys |
| 31 | +from collections.abc import Mapping |
| 32 | +from pathlib import Path |
| 33 | +from typing import Any |
| 34 | + |
| 35 | +from sync.signals.stripe.symbols import build_symbol_map |
| 36 | + |
| 37 | +# Run as `uv run python scripts/stage_symbol_map.py`, Python puts `scripts/` on the path and not |
| 38 | +# the repository root, so the sibling module below is unimportable by the name the tests use. |
| 39 | +# The same line for the same reason as in `scripts/score_corpus.py`. |
| 40 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
| 41 | + |
| 42 | +from scripts.symbol_map_pin import PIN, SymbolMapMismatch, read_pin, symbol_map_digest |
| 43 | + |
| 44 | +SPEC = Path(".cache/specs/v2330.json") |
| 45 | + |
| 46 | + |
| 47 | +def stage_symbol_map(spec: Path, into: Path, pin: Mapping[str, Any]) -> str: |
| 48 | + """Build the map from `spec` and write it to `into`, or refuse and write nothing. |
| 49 | +
|
| 50 | + The check is before the write, and a refusal leaves whatever was already staged untouched. |
| 51 | + `.cache/` is per-worktree space several workers share, so a rebuild that truncated the file |
| 52 | + on its way to failing would break a run that was scoring correctly. |
| 53 | + """ |
| 54 | + mapping = build_symbol_map(json.loads(spec.read_text(encoding="utf-8")), None) |
| 55 | + digest = symbol_map_digest(mapping) |
| 56 | + if digest != pin["digest"]: |
| 57 | + raise SymbolMapMismatch( |
| 58 | + f"{spec} builds a map at digest {digest[:12]} with {len(mapping)} symbols; the " |
| 59 | + f"corpus records {pin['digest'][:12]} with {pin['symbols']}. Re-pinning is a " |
| 60 | + f"deliberate act with a measurement attached -- see {PIN}." |
| 61 | + ) |
| 62 | + |
| 63 | + into.parent.mkdir(parents=True, exist_ok=True) |
| 64 | + into.write_text(json.dumps(mapping), encoding="utf-8") |
| 65 | + return digest |
| 66 | + |
| 67 | + |
| 68 | +def main() -> int: |
| 69 | + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) |
| 70 | + parser.add_argument("--spec", default=str(SPEC), help="the specification to build from") |
| 71 | + parser.add_argument("--pin", default=str(PIN), help="the pin the built map must match") |
| 72 | + args = parser.parse_args() |
| 73 | + |
| 74 | + pin = read_pin(Path(args.pin)) |
| 75 | + try: |
| 76 | + digest = stage_symbol_map(Path(args.spec), Path(pin["staged_at"]), pin) |
| 77 | + except (OSError, SymbolMapMismatch) as exc: |
| 78 | + print(f"refused: {exc}") |
| 79 | + return 2 |
| 80 | + |
| 81 | + print(f"{pin['staged_at']} {digest} {pin['symbols']} symbols") |
| 82 | + return 0 |
| 83 | + |
| 84 | + |
| 85 | +if __name__ == "__main__": |
| 86 | + raise SystemExit(main()) |
0 commit comments