Carry the UAST structural facts across the Rust/Python boundary - #26
Open
n1ckyb wants to merge 1 commit into
Open
Carry the UAST structural facts across the Rust/Python boundary#26n1ckyb wants to merge 1 commit into
n1ckyb wants to merge 1 commit into
Conversation
…undary The cross-language structural facts (#9) were computed by the engine and thrown away before any caller saw them. uast.rs emits three facts for function entities - early_exit_count, negated_condition_count, has_guard_clause - and asserts them in 12 of its own Rust tests, all passing. NodeFacts carried no fields for any of them, so pydantic dropped them silently at the DTO boundary. Measured before this change, on a function with two early returns: early_exit_count = ABSENT These are the first facts derived from the PRUNED CANONICAL TREE rather than from a per-language extractor, so they are available for EVERY language rather than only the ones with bespoke support - exactly the facts a non-Python binding would rely on, and they reached nobody. Verified after the change, on a real guard clause: def send(msg): if not msg: negated_condition_count = 1 return None early_exit_count = 1 return deliver(msg) has_guard_clause = True has_guard_clause stays tri-state on purpose. The pruned tree can prove a guard is PRESENT but cannot prove one is ABSENT - statement order survives pruning, operators do not - so None means "not determinable here", never "no guard". Collapsing it to False would let a consumer draw a conclusion the evidence does not support. Adds Python acceptance tests, including one asserting the FIELDS EXIST on the model at all. The defect was never a wrong value; it was a missing field, and pydantic drops unknown keys without complaint. Proven to bite: removing the field fails all four, restoring it passes all four. Full suite: 2412 passed, 0 failed (2408 before, +4 new). The wider lesson is about the certified-path policy. Rust being authoritative does not help if the value never reaches the caller, and nothing on either side was watching the join - a Rust test proving a fact is COMPUTED and a Python test proving it is DELIVERED are different assertions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
n1ckyb
added a commit
that referenced
this pull request
Aug 10, 2026
Both exist in the monorepo and have NEVER run in this repo's CI. ## test_rename_does_not_hide_behaviour.py (3 tests) The acceptance tests for intentumdiff-core#18: a rename that also changed the body was collapsed into a single REFACTORING, discarding the behavioural change and telling the reviewer nothing had changed. That is the worst output this engine can produce, and the guard against it has never executed here. Gated on the engine fix, which is verified present on core's RC (`carries_entity_name` in draft_suppressors.rs) before syncing - landing the test first would have arrived red. ## test_live_server_asset_diff.py (13 tests) Covers the live-server asset_diff op. Verified the op exists on this branch (5 references in live_server.py) before syncing, same reason. ## Held back deliberately test_uast_structural_facts.py is NOT in this commit. Its 4 tests need the early_exit_count / negated_condition_count / has_guard_clause fields on NodeFacts, which are still only on the unmerged fix/uast-facts-cross-boundary branch. Landing them now would put 4 red tests on the release branch. They follow once #26 merges. ## Verification note These cannot be validated from a bare clone: running them here raises PluginNotFoundError('unknown') because parser components are not staged outside CI - the condition tracked as #22. CI is the verification, which is the standing model for this repo (the monorepo has no CI at all). Found by auditing why the CI suite collects 84 fewer tests than the monorepo. Co-authored-by: n1ckyb <nicknuxton@icloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The engine computed three facts for every language and Python threw them away.
uast.rsemitsearly_exit_count,negated_condition_countandhas_guard_clausefor function entities, and asserts them in 12 of its own Rust tests — all passing.NodeFactscarried no fields for any of them, so pydantic dropped them silently at the DTO boundary.Measured before, on a function with two early returns:
Why these matter more than three fields
They are the first facts derived from the pruned canonical tree rather than a per-language extractor — so they are available for every language, not only the ones with bespoke support. That makes them precisely the facts a Go or Java binding would rely on, and they reached nobody.
Verified after the change, on a real guard clause:
has_guard_clauseis deliberately tri-stateThe pruned tree can prove a guard is present but cannot prove one is absent — statement order survives pruning, operators do not. So
Nonemeans "not determinable here", never "no guard". Collapsing it toFalsewould let a consumer draw a conclusion the evidence does not support.The tests
Four acceptance tests, including one asserting the fields exist on the model at all. The defect was never a wrong value — it was a missing field, and pydantic drops unknown keys without complaint.
Proven to bite: removing the field fails all four; restoring it passes all four.
Verification
Full suite: 2412 passed, 0 failed (2408 before, +4 new).
The wider lesson
Rust being the certified path does not help if the value never reaches the caller, and nothing on either side was watching the join. A Rust test proving a fact is computed and a Python test proving it is delivered are different assertions — this repo had the first and not the second.
🤖 Generated with Claude Code