Skip to content

compass(tests): one recursive walk for every spec site check - #272

Merged
jgong5 merged 1 commit into
feature/atomcompass_newfrom
compass/issue-218-spec-walks
Sep 23, 2026
Merged

jgong5 merged 1 commit into
feature/atomcompass_newfrom
compass/issue-218-spec-walks

Conversation

@jgong5

@jgong5 jgong5 commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Closes #218

Which findings were still live at the tip

Measured on node 18 (xiaobizh_n18_cpu), tests/compass/test_spec_schema.py alone, on git archive copies with atom.__file__ printed under each staged root. First at 308922c5c, then re-measured at ff9617f30 after #263 and #268 landed mid-task (both touch atom/compass/spec/, which the guard walks). The two tips gave the same results.

probe tip ff9617f30 branch f70f15c7a
null control 139 passed, RC=0 139 passed, RC=0
A: genuine raise SpecRefusal(Rule.SHAPE, ...) at atom/compass/spec/sub/extra.py:6, relative import like the package's own modules 140 passed, RC=0. The guard is blind; the extra pass is the rglob import check picking the module up 1 failed, test_every_site_that_declines_a_document_is_driven_here: Extra items in the right set: ('sub/extra.py', 6)
SHAPE site at atom/compass/spec/sub/machine.py:119, the same basename and line as the real SHAPE site machine.py:119 140 passed, RC=0 1 failed, same test: Extra items in the right set: ('sub/machine.py', 119)
B: stray raise ValueError(value) appended at machine.py:338 1 failed, test_every_refusal_site_in_the_package_resolves_to_a_rule: Extra items in the left set: ('machine.py', 338) identical
  • A was live. Three site walks (_refusal_sites, _mentions_the_walk_cannot_follow, _built_where_thrown) used PACKAGE.glob("*.py"). The import check about 700 lines further down used rglob.
  • B was already fixed by compass(spec): the version refusal asks for a reader, not an edit #212's later rounds. The count became assert thrown <= set(_refusal_sites()), and pytest diffs that item by item. This PR does not change it.

How the walks now agree by construction

  • One walk. _spec_modules() is now defined once, above the first walk, and is the only place the package is listed. All four readers of the package source take their modules from it: the three site walks and the parametrised import check. Before, there were four independent glob/rglob calls. For the walks to diverge again, someone would have to write a second listing next to one that already exists.
  • One name per site. Once the walk recurses, source.name can no longer key a site, because sub/machine.py:119 and machine.py:119 would be the same key. That is the second probe above: green on the tip only because the walk never got that far. With a plain glob → rglob swap, that site would collide with a real one and stay green.
    • _named(path) keys every site by its path relative to the package.
    • _site_of names the site it records in the same way, so the driven set and the walked set use one naming scheme. Flat modules keep their bare file name, so unknown[0] == "explain.py" still holds.
    • A module loaded from outside the package walked here makes relative_to raise, instead of being compared under a name that happens to match.
  • The import check's parametrise ids and its failure message now use the same relative name, so sub/extra.py is not shown as extra.py.

Gates

Gate 1: ATOM's suite, unmodified. scripts/compass/gate_cpu.sh, each tree's own copy, run with stamps on node 18, one gate at a time, each bounded with timeout -k 10 900.

commit stamp result GATE_CPU_RC
control ff9617f30 5120 passed, 149 skipped, 3 xfailed 0
branch f70f15c7a 5120 passed, 149 skipped, 3 xfailed 0
  • Node-id delta, from --junitxml on both sides: 5272 ids each, none added, none removed, none changed state.
  • Diff: production 0 lines; test tests/compass/test_spec_schema.py +24/−15. black and ruff are clean on the file.
  • Merge check: git merge-tree --write-tree ff9617f30 f70f15c7a gives f979c3f47293b6aa5179dd695e30fb510beeb02e, clean.

Gate 2: CPU-only tests. test_spec_schema.py is the test. It is 139 passed on both sides, inside the gate run above.

Gate 3: named result. See the table above: a submodule SHAPE site reddens the guard and is named by file and line, and the same site leaves the tip green.

Gate 4: independent review. Pending; the coordinator dispatches the reviewer.

Left undone

  • Nothing in scope.
  • atom/compass/spec/ is still flat, so the recursive walk has no second module to read today. The probes above are the only evidence that it reaches one.

🤖 Generated with Claude Code

The site checks in test_spec_schema.py walked the spec package with
PACKAGE.glob("*.py") while the import check in the same file used rglob,
so a refusal site in a submodule was read by one and invisible to the
other: a genuine Rule.SHAPE raise at spec/sub/extra.py left the file green.

_spec_modules() is now defined once, above the first walk, and every walk
takes its modules from it. Site keys are the path relative to the package
(via _named), so a submodule file cannot share a key with a top-level
module of the same name, and _site_of names a site the same way.

Closes #218

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# A site's file, relative to the package, so `sub/machine.py` is not
# `machine.py`. A module loaded from outside the package walked here
# refuses rather than being compared under a name that happens to match.
return pathlib.Path(path).resolve().relative_to(PACKAGE).as_posix()

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, noted for the record (principle 6: working as intended). relative_to(PACKAGE) has one consequence beyond sub-module keying, and I want it recorded.

PACKAGE comes from this test file's __file__, but _site_of passes co_filename, which comes from wherever atom was actually imported. Suppose a run's atom resolves to a different root than the tests, for example a stale PYTHONPATH or an installed copy. At the tip, the four _site_of tests compared basenames across two different trees and could pass. At this head, recording raises ValueError: ... is not in the subpath of ... inside pytest.raises(SpecRefusal), so those tests error and name both paths.

That is the refusal the comment above promises. It also catches the node-18 PYTHONPATH-overrides-the-tree hazard for free. I'm accepting it as a refusal, not a fallback.

The next task in this area should know that this file cannot be run against an installed atom. tests/compass/test_memory_readings.py takes the other approach and derives its PACKAGE from the module's __file__.

@@ -394,7 +394,6 @@ def written(path, value):
"one field written under two spellings": lambda: both_spellings(True),
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, non-blocking (principle 3). This hunk deletes one of the two blank lines between BREAKAGES and the ELSEWHERE comment block. It has nothing to do with the walk. black and ruff accept either form, so leave it or restore it. It is not worth a cycle.

@jgong5

jgong5 commented Sep 23, 2026

Copy link
Copy Markdown
Owner Author

Review of PR #272 (issue #218, finding A)

Verdict: APPROVE at head f70f15c7aa5c373791b54f9c399fbf02032fc7a1

No blocking issues. There are two non-blocking inline notes: one reservation on _named for the record (line 419), and one whitespace nit (line 396).

I read the eight design principles in atom/compass/design/README.md and atom/compass/AI_DEV_RULES.md first.

1. A is fixed by construction (principle 6)

Ruling: yes. _spec_modules() is the only listing of atom/compass/spec/ in tests/compass/, at test_spec_schema.py:412. All four readers take their modules from it:

  • _refusal_sites (478);
  • _mentions_the_walk_cannot_follow (514);
  • _built_where_thrown (560);
  • the parametrised import check (1199).

I ran git grep at the head for glob(, iterdir, listdir, os.walk and scandir over tests/compass/. Nothing else walks the spec package. The other hits are:

  • other packages' own walks: backends, clock, ir, kv, runner, memory;
  • tmp_path walks;
  • two whole-atom/ scans for unrelated call sites, in test_capture_real_model.py:2139 and test_runner_rpc_surface.py.

Both key sources go through one function, _named: the static walks via _named(source) and the dynamic _site_of via _named(co_filename). The walked set and the driven set can no longer disagree about which modules exist, or about what a site is called.

2. Named result: my own mechanisms, not the developer's fixtures

Node 18 xiaobizh_n18_cpu, on git archive trees staged with docker exec -i ... tar -x:

  • the tarball md5 matched on both ends (tip cb4989ac..., head 02cfbea8...);
  • atom.__file__ printed under the staged copy on every run;
  • only tests/compass/test_spec_schema.py was run, with timeout -k 10 300.

My fixtures differ from the developer's. sub/ is a real subpackage with an __init__.py. The SHAPE site picks its rule through a local name (rule = Rule.SHAPE; raise SpecRefusal(rule, ...)), which also exercises the binding resolver.

probe tip ff9617f30 head f70f15c7a
null control 139 passed, rc=0 139 passed, rc=0
SHAPE raise in spec/sub/extra.py:7, through a local 141 passed, rc=0 (blind) rc=1: test_every_site_that_declines_a_document_is_driven_here FAILED, Extra items in the right set: ('sub/extra.py', 7)
SHAPE raise in spec/sub/machine.py:119, the same basename and line as the real SHAPE site machine.py:119 141 passed, rc=0 (blind) rc=1: same node id, Extra items in the right set: ('sub/machine.py', 119)
same collision, head with _named mutated to Path(path).name (rglob kept) n/a 141 passed, rc=0. The collision is masked.

The last row settles the path-keying question. A plain glob to rglob swap would have stayed green on a real uncovered site, so keying by relative path is load-bearing (principle 6).

In every red run, the other site tests at the head still passed: resolves_to_a_rule, explain_declines..., and the_version_is_the_one_site....

There is one further tip defect that the head fixes. With sub/machine.py present, the tip's import check parametrised as [machine.py0], [machine.py1], [__init__.py0] and [__init__.py1]: pytest's de-dup of two identical ids. The head names them [machine.py], [sub/machine.py] and [sub/__init__.py].

3. B is already fixed at the tip: confirmed

I appended a stray def _stray_probe(value): raise ValueError(f"cannot read {value!r}") to validate.py, landing at line 417. The result was identical on both sides: rc=1, with test_every_refusal_site_in_the_package_resolves_to_a_rule FAILED on Extra items in the left set: ('validate.py', 417), and 138 passed on each.

Extra probe: the same stray raise ValueError(value) in spec/sub/stray.py.

  • Tip: 141 passed, blind.
  • Head: the same node id FAILED, on ('sub/stray.py', 2).

So the recursion also covers the count check, not only the SHAPE partition.

4. Key stability for the flat package

I loaded the tip's and the head's test_spec_schema.py as two modules against the same staged head tree, and compared every key by value:

  • _refusal_sites(): 45 = 45, equal dicts.
  • _built_where_thrown(): 43 = 43, equal.
  • _mentions_the_walk_cannot_follow(): [] = [].
  • _site_of for all 7 BREAKAGES and both ELSEWHERE entries: identical tuples on both sides. For example, ('merge.py', 114) and ('validate.py', 283) for ELSEWHERE, and ('machine.py', 119) for the missing required field.
  • _sites_naming, all identical:
    • SHAPE has 9 sites;
    • ADDRESSING is explain.py:207 and rules.py:117;
    • TOTALITY is rules.py:145 and rules.py:170;
    • VERSION is machine.py:154.
  • Parametrise ids: identical lists, from __init__.py through validate.py.
  • --collect-only node ids for the file: 139 = 139, diff empty.

No partition entry moves.

5. No design-doc references

I checked the added lines for D-numbers, principle numbers, gate labels and issue or PR numbers: none found. The comments say what the code does.

Gate: the tree that will land

  • Tip re-read after git fetch: fork/feature/atomcompass_new = ff9617f3052c93067a2f56a663ef2cea5add4c28, unchanged.
  • git merge-tree --write-tree ff9617f30 f70f15c7a gives f979c3f47293b6aa5179dd695e30fb510beeb02e, clean. The merge-base is the tip, and the head's own tree is the same object, so the head archive is the tree that lands.
  • Gated once, on node 18, with the tree's own scripts/compass/gate_cpu.sh:
    • run with timeout -k 10 2400 and no pipe;
    • stamps: .compass-commit = the head sha, and .compass-changed = tests/compass/test_spec_schema.py;
    • the gate printed commit: f70f15c7a (stamp) and gpu: not required;
    • atom.__file__ = /tmp/p272rev/head/ATOM/atom/__init__.py.
    • 5120 passed, 149 skipped, 3 xfailed, 0 failed, GATE_CPU_RC=0 (165 s).

This matches the developer's control and branch measurements of 5120 and 0 delta. There were no failures, so the test_stream_marker_properties.py timing classes did not come into play.

The staging was removed afterwards, and nothing was written to the shared mount.

What the next task in this area should watch

  • This file now refuses to run when atom imports from a root other than the test file's own. See the inline note at line 419. That is intended, but it rules out running against an installed copy.
  • atom/compass/spec/ is still flat. The probes above are the only evidence that the recursive walk reaches a second level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant