Skip to content

Commit 9cef5ec

Browse files
committed
T1.24b: the bridge freeze reads the checked tree's allowances
bridge_freeze.py granted allowances from the running copy of itself. The gate runs its checkpoint's copy over a candidate's tree, so it granted none of the candidate's, and an allowance landed beforehand is spent at the base and grants nothing either: no allowance could ever pass the gate. Shown by T1.24b's candidate (CI run 38205702823): every check but the gate's l0, which refused the three setters' allowances under E66(g). problems(), which l0_deps.py runs, now reads the checked tree's own copy of ALLOWANCES (tree_allowances; a copy missing, unparsed or not a literal is refused). So a candidate's own ALLOWANCES can grant an allowance. The safeguards: each entry must name a row the owner approved in the base's decisions.md; bridge_freeze.py is a protected path a landing must name; reviewers check every entry; E56(d) retires the allowances at T1.28b. selftest.py seeds the reading (the tree's allowances, not the running copy's; not a literal; the script missing) and fixes a self-test bug: its allowance seeds replaced only an empty ALLOWANCES literal, so they failed once the script held any entry (allowances_as replaces the whole assignment). A bug fix to a frozen guard (E56(a)), part 1 of T1.24b; it adds no allowance. Refs: T1.10e, T1.14 Signed-off-by: Adi Wadaskar <awadaskar@nvidia.com>
1 parent b455b94 commit 9cef5ec

2 files changed

Lines changed: 110 additions & 10 deletions

File tree

‎scripts/rust_core/bridge_freeze.py‎

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@
8181
under the entry's own ``ALLOWANCES`` entry, declared ``allowance``: one
8282
the base's copy of this file does not already hold (spent), under a row of
8383
the base's decisions.md whose status reads "approved by owner <date>" and
84-
which names ``<file>::<qualified name>`` in backticks. The one exception:
84+
which names ``<file>::<qualified name>`` in backticks; the allowances
85+
read are the checked tree's own copy of this file's, so a gate running its
86+
checkpoint's copy over a candidate reads the candidate's (a bug fix at
87+
T1.24b: it read its own, and granted none). So a candidate's own
88+
ALLOWANCES can grant an allowance; the safeguards are that each entry must
89+
name a row the owner approved in the base's decisions.md, that this file is
90+
a protected path a landing must name, that reviewers check every entry, and
91+
that E56(d) retires the allowances at T1.28b. The one exception:
8592
the entries declared ``moved`` in one change net their counts, and their
8693
dropped calls, against each other across the frozen files, so a rule
8794
moved from ``schema.py`` into ``_rust/`` glue needs no allowance, as long
@@ -903,6 +910,52 @@ class Base:
903910
source: Callable[[Path], str | bytes | None] = lambda rel: None
904911

905912

913+
def literal_allowances(
914+
script: str | None, whose: str
915+
) -> tuple[dict[str, dict[str, dict[str, Any]]] | None, list[str]]:
916+
"""The ``ALLOWANCES`` literal a copy of this file holds (``whose``
917+
copy), or why it cannot be read."""
918+
if script is None:
919+
return None, []
920+
try:
921+
tree = ast.parse(script)
922+
except SyntaxError as error:
923+
return None, [f'{whose} {SCRIPT} does not parse: {error.msg}']
924+
for node in tree.body:
925+
target = (
926+
node.target
927+
if isinstance(node, ast.AnnAssign)
928+
else node.targets[0]
929+
if isinstance(node, ast.Assign) and len(node.targets) == 1
930+
else None
931+
)
932+
if isinstance(target, ast.Name) and target.id == 'ALLOWANCES':
933+
try:
934+
value = ast.literal_eval(node.value) # type: ignore[arg-type]
935+
except ValueError:
936+
break
937+
if isinstance(value, dict) and all(
938+
isinstance(v, dict) for v in value.values()
939+
):
940+
return value, []
941+
break
942+
return None, [f'{whose} {SCRIPT}: ALLOWANCES is not a literal dict']
943+
944+
945+
def tree_allowances(root: Path) -> tuple[Allowances, list[str]]:
946+
"""The allowances of the tree at ``root``: its own copy of this file's
947+
``ALLOWANCES``, which a gate, running its checkpoint's copy of this
948+
file over a candidate's tree, reads in place of its own (T1.24b: the
949+
checkpoint's copy holds none of the candidate's growth); a copy
950+
missing, unparsed or not a literal is refused."""
951+
path = root / SCRIPT
952+
script = path.read_text(encoding='utf-8') if path.is_file() else None
953+
if script is None:
954+
return {}, [f"the tree's {SCRIPT} is missing"]
955+
found, problems = literal_allowances(script, "the tree's")
956+
return found or {}, problems
957+
958+
906959
def held_allowances(
907960
script: str | None,
908961
) -> tuple[set[tuple[str, str]], list[str]]:
@@ -1322,7 +1375,10 @@ def problems(root: Path = REPO_ROOT, ref: str | None = None) -> list[str]:
13221375
ref = base_ref(ref)
13231376
if not ref.strip():
13241377
return [f'the base branch is blank (--base or {BASE_ENV}): name one']
1325-
return check(root, base_baseline(root, ref))
1378+
allowances, unreadable = tree_allowances(root)
1379+
if unreadable:
1380+
return unreadable
1381+
return check(root, base_baseline(root, ref), allowances=allowances)
13261382

13271383

13281384
def write(root: Path) -> list[str]:

‎scripts/rust_core/selftest.py‎

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,23 @@ def source(rel: Path) -> str | None:
653653
path = REPO_ROOT / rel
654654
return path.read_bytes() if path.is_file() else None
655655

656+
def allowances_as(value: str) -> str:
657+
r"""The script with its whole ALLOWANCES assignment, whatever it
658+
holds (empty until T1.24b's E66 entries), assigning ``value``."""
659+
node = next(
660+
each
661+
for each in ast.parse(script).body
662+
if isinstance(each, ast.AnnAssign)
663+
and isinstance(each.target, ast.Name)
664+
and each.target.id == 'ALLOWANCES'
665+
)
666+
assigned = ast.get_source_segment(script, node)
667+
assert assigned is not None
668+
return script.replace(
669+
assigned,
670+
f'ALLOWANCES: dict[str, dict[str, dict[str, Any]]] = {value}',
671+
)
672+
656673
def based(
657674
rows: str = '',
658675
*,
@@ -661,10 +678,7 @@ def based(
661678
) -> bridge_freeze.Base:
662679
text = script
663680
if held is not None:
664-
text = text.replace(
665-
'ALLOWANCES: dict[str, dict[str, dict[str, Any]]] = {}',
666-
f'ALLOWANCES: dict[str, dict[str, dict[str, Any]]] = {held}',
667-
)
681+
text = allowances_as(held)
668682
assert text != script
669683
declared = changes
670684
if spent is not None:
@@ -1877,15 +1891,45 @@ def row(status: str, names: str = f'`{key}`') -> str:
18771891
'the fixture base',
18781892
committed,
18791893
decisions,
1880-
script.replace(
1881-
'ALLOWANCES: dict[str, dict[str, dict[str, Any]]] = {}',
1882-
'ALLOWANCES: dict[str, dict[str, dict[str, Any]]] = dict()',
1883-
),
1894+
allowances_as('dict()'),
18841895
changes,
18851896
source,
18861897
),
18871898
)
18881899

1900+
# T1.24b: the allowances are the tree's own copy's, which a gate reads
1901+
# when it runs its checkpoint's copy of the script over a candidate.
1902+
with tempfile.TemporaryDirectory(prefix='sdm-allowances-') as tmp:
1903+
root = Path(tmp)
1904+
(root / bridge_freeze.SCRIPT).parent.mkdir(parents=True)
1905+
mine = {
1906+
'E99': {
1907+
'x.py::f': {'raise': 1, 'warn': 0, 'branch': 0, 'calls': []}
1908+
}
1909+
}
1910+
(root / bridge_freeze.SCRIPT).write_text(allowances_as(repr(mine)))
1911+
read, unreadable = bridge_freeze.tree_allowances(root)
1912+
expect(
1913+
results,
1914+
"bridge_freeze: the tree's allowances, not the running copy's",
1915+
[] if read == mine and not unreadable else [repr(read)],
1916+
fails_with=None,
1917+
)
1918+
(root / bridge_freeze.SCRIPT).write_text(allowances_as('dict()'))
1919+
expect(
1920+
results,
1921+
"bridge_freeze: the tree's allowances not a literal",
1922+
bridge_freeze.tree_allowances(root)[1],
1923+
fails_with='ALLOWANCES is not a literal dict',
1924+
)
1925+
(root / bridge_freeze.SCRIPT).unlink()
1926+
expect(
1927+
results,
1928+
"bridge_freeze: the tree's script missing",
1929+
bridge_freeze.tree_allowances(root)[1],
1930+
fails_with='is missing',
1931+
)
1932+
18891933
# Missing, empty and retyped evidence.
18901934
def write_baseline(text: str) -> Callable[[Path], None]:
18911935
return lambda r: (r / bridge_freeze.BASELINE).write_text(text)

0 commit comments

Comments
 (0)