Repository navigation
Add scope hierarchy resolution - #91
Merged
Merged
Conversation
Closes #79. Scope is a tree - Product then Deployment then Program, and a fourth level is plausible - so nothing in scopes.py counts or names levels. Depth is reported and never branched on, which is what makes adding a level a row in the scope table rather than a schema change. Several roots are allowed: a synthetic "everything" node above them would invite a record attached to it, inherited by products that share nothing. Only divergences are stored, which is what makes the grain affordable - a product with ~200 fields across a dozen deployments implies five-figure logical rows and holds hundreds. Of the three divergence kinds only removed is stored, as a tombstone, because an absence cannot mean "this deployment does not collect this" when absence is already what inheritance means. added and overridden are the same stored thing and differ only in what sits above, so divergences() derives the label from the tree rather than trusting one the author typed - and it moves on its own when an ancestor acquires the same key, which is correct. A removal is never itself an effective record, so the register cannot print "processes face_scan (removed)" as a row; it appears in the divergence view instead. A scope may re-declare what an ancestor removed, and that reads as added, since the parent having removed it does not have it. Inherited records carry the provenance contract shared with #82, with from set to the scope that declared the record rather than the immediate parent - that is the scope you would edit to change what this one shows. Confidence and confirmation carry across untouched; the resolver mutates nothing it is given. A missing grain dimension is a key value, not a wildcard. Wildcards would make override order depend on specificity as well as depth, and two orderings that can disagree is one too many. 34 new tests. Each guard was verified by breaking it: twelve mutations to scopes.py - unreversed ancestry, ignored tombstones, everything marked inherited, no cycle check, unstable ordering, a grain missing two dimensions, resolve_all not seeded from the parent - and every one was caught. 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.
Advances #79 (does not close it — this is the resolution library; the storage layer and UI are still to come).
src/pantograph/scopes.py— a pure library, no database, Dash or config imports. Given a scope tree and the records attached at various levels, compute the effective records for any scope. Scope isProduct → Deployment → Program → …with no fixed depth, so a fourth level needs no schema change.Purely additive: no existing file is touched, so nothing on
maincan regress. 183 → 217 tests.Design calls a reviewer should check
Only
removedis stored;addedandoverriddenare derived. This contradicts the issue's wording, and the agent is right that the issue was wrong.removedmust be written down because absence already means "inherited". Butaddedandoverriddenare the same stored thing — a record declared here — differing only in whether an ancestor also declares that key. Store the label and it goes stale: the moment a product acquires a field a deployment had added, a storedaddedbecomes a lie.Re-declaring a key an ancestor removed currently reads as
added. Arguable, and worth a ruling: "Maharashtra added face_scan" versus "Maharashtra overrode India's removal of face_scan". The second is more informative to a regulator and might justify a fourth label,restored. Kept at three labels until someone needs a fourth.Multiple inheritance is unsupported and should stay so. Precedence between two ancestor chains has no natural answer and every scheme is a rule someone must memorise.
A missing grain dimension is a key value, not a wildcard. Wildcards make precedence depend on both specificity and depth, and those orderings can disagree.
A removal is never an effective record.
resolvereturns what a scope actually processes; tombstones surface only viadivergences. Returning them in the row list invites a UI that prints "processes face_scan (removed)", which is the credibility failure the model warns about.Verification
Failure capability checked by mutation, not assumed: twelve deliberate breakages, each confirmed to fail the suite.
Not verified: no integration with storage, config or UI, since none exists yet. The record key names are the agent's invention and may need renaming when the scope tables land.
🤖 Generated with Claude Code