Add a scope model and selector mechanism for carried rules (#368, PR-1) - #369
Merged
Conversation
Foundational, deterministically inert step toward giving every governance rule
a single explicit scope so carried docs are granular single-scope pieces
composed per repo, not large pieces with internal carve-outs.
- spec/scope-model.md (new, hub-only, not carried): defines the two axes
(host vs repo home; hub-only / all-downstream / type-specific reach) and the
appliesTo selector vocabulary - four disjoint namespaces (project types,
workflowModel, releaseTrigger, consumerModel) - with the any-of and
entry-AND-section semantics. Records CODESTYLE's section->scope mapping.
- spec/files.schema.json: a sections element may now be a bare string or a
{name, appliesTo} object (oneOf), so a section can be scoped, not just a file.
Backward-compatible - every existing entry stays valid.
- spec/audit.py: match appliesTo against a repo's full selector set
(repo_selectors: types + workflowModel + releaseTrigger + consumerModel), and
replace the hardcoded operational develop.json path swap with two data
entries in files.json (appliesTo release vs operational). The namespaces are
disjoint and no current appliesTo token is non-star, so the required-file set
is unchanged for all 21 cataloged repos (verified by diffing old vs new).
- spec/validate.py: first pass over files.json - every appliesTo token must
resolve to a known selector, no project type may collide with a reserved
token, and every cataloged repo must declare consumerModel (push/pull) so a
push/pull-scoped section cannot fail open.
- AUDIT.md section 3 + files.json note: one-line pointers to the scope model.
No fleet-finding delta: this PR adds capability and codifies the current state.
The section-presence check, the doc surgery, and carrying Verification
Discipline follow in later PRs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces a scope model and selector mechanism (appliesTo) for carried governance files/sections, enabling per-repo composition without changing current fleet findings.
Changes:
- Add scope-selector vocabularies and
files.jsonselector validation tospec/validate.py. - Add selector-aware matching to
spec/audit.py, including moving the operationaldevelop.jsonswap from code intospec/files.jsondata. - Add the
appliesTomodel documentation (spec/scope-model.md) and extendspec/files.schema.jsonto allow per-section selector objects.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/validate.py | Adds selector vocab + validates consumerModel presence and files.json appliesTo tokens. |
| spec/scope-model.md | Documents the scope model, selector namespaces, and appliesTo semantics. |
| spec/files.schema.json | Extends schema to allow sections entries as strings or { name, appliesTo } objects. |
| spec/files.json | Adds selector-scoped baseline entries (notably release vs operational develop rulesets) and documents appliesTo. |
| spec/audit.py | Computes repo selector sets and applies appliesTo matching when auditing baseline file presence. |
| AUDIT.md | Points readers to the scope model when explaining applicability for carried files/sections. |
…#369) The repo doc convention (AGENTS.md) requires reference-style links in every markdown file except the agent-instruction files. Convert spec/scope-model.md's inline links to reference-style with a definitions block, and change the new AUDIT.md pointer to the reference-style [scope-model] with its definition added alphabetically to the Repo group. Both Copilot round-1 findings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
spec/files.schema.json:40
- The top-level entry "appliesTo" schema is also unconstrained for array element types. Restrict it to "string" or "array of strings" for consistency with the section-level form and to prevent non-string tokens from reaching runtime matchers.
"intentRef": { "type": "string" },
"appliesTo": { "type": ["string", "array"] }
}
…ons (#369) Copilot round-2 findings: - files.schema.json: appliesTo array items are now constrained to strings at both the entry level and the new section-object level, so a non-string token cannot reach the runtime matchers. - validate.py: check_selector guards a non-string token explicitly (CI runs no JSON-schema validation) instead of crashing on set membership of an unhashable value. - scope-model.md, audit.py, AUDIT.md: replace clause-joining semicolons in the new prose and docstring with the house comma/two-sentence form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot round-3 findings: scope-model.md and the repo_selectors docstring claimed every selector resolves repo -> defaults -> fleet default, but consumerModel has no fleet default - validate.py requires it on every cataloged repo instead. State each selector's resolution accurately: workflowModel and releaseTrigger fall back to a fleet default (release, two-phase); consumerModel is required per cataloged repo, so a cataloged repo always contributes one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot round-4 finding: check_selector accepted an empty appliesTo list, which scopes an entry or section to no repo at all (it fails open - silently applies nowhere). Reject [] in validate.py with a clear message, and add minItems:1 to both appliesTo definitions in files.schema.json so an empty list is also a schema violation. Use "*" for all repos, or list selectors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot round-5 findings: releaseTrigger is now a scope selector, but validate checked only workflowModel, so an invalid releaseTrigger would silently fail to match any releaseTrigger-scoped section instead of erroring. Add per-repo and defaults.releaseTrigger validation against RELEASE_TRIGGERS, mirroring the workflowModel checks, and drive all three model/trigger/consumer checks off the vocabulary constants so the messages and membership tests share one source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#369) Copilot round-6 findings: three scope-model.md headings were sentence-case (AGENTS.md title-case rule) and the CODESTYLE section-scope list used semicolons between comma-free items, reading as clause-joining. Title-case the headings and switch the list to commas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
spec/audit.py:100
- repo_selectors() falls back to defaults.get('consumerModel'), but registry/repos.schema.json does not allow defaults.consumerModel (defaults has additionalProperties:false and only defines groundTruthBranch/releaseTrigger/workflowModel). Keeping a defaults fallback here is dead code and can mislead future editors about supported registry shape; prefer to read only the per-repo consumerModel (which validate.py already requires for cataloged repos).
sel.add(entry.get("workflowModel") or defaults.get("workflowModel") or "release")
sel.add(entry.get("releaseTrigger") or defaults.get("releaseTrigger") or "two-phase")
cm = entry.get("consumerModel") or defaults.get("consumerModel")
if cm:
sel.add(cm)
Copilot round-7 low-confidence finding (correct): repo_selectors fell back to
defaults.get("consumerModel"), but registry/repos.schema.json's defaults object
is additionalProperties:false with only groundTruthBranch/releaseTrigger/
workflowModel, so defaults.consumerModel can never exist - the fallback was dead
code that misrepresents the supported registry shape. Read only the per-repo
consumerModel (validate.py requires it on every cataloged repo); the guard stays
to shield a malformed non-cataloged entry. Still inert: required-file set
unchanged for all 21 repos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot round-8 finding: the files.json validation loop exists because CI runs no JSON-schema validation, but it assumed well-formed input - a non-array baseline, a non-object entry, a non-array sections, or a non-string/non-object section element would raise AttributeError or iterate a string instead of reporting a clear validation error. Guard each shape and continue, mirroring the existing secrets.json shape checks. Verified all four malformed shapes now exit 1 with a clear message and no traceback. Co-Authored-By: Claude Opus 4.8 <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.
What and why
First of four staged PRs for #368: give every governance rule a single explicit scope, so carried docs become granular single-scope pieces composed per repo rather than large pieces with internal carve-outs a reader must piece out. This PR is the inert foundation — it adds the mechanism and documents the model, with zero fleet-finding delta. Policy changes (turning on section-presence checking, the doc surgery, carrying Verification Discipline) follow in later PRs, each held before
main.Changes
spec/scope-model.md(new, hub-only, not in the carried baseline): the two axes — home (host vs repo) and reach (hub-only / all-downstream / type-specific) — and theappliesToselector vocabulary: four disjoint namespaces (project types,workflowModel,releaseTrigger,consumerModel). States the load-bearing semantics:appliesTois any-of (cross-axis AND is deliberately not expressible — that's the signal to split a piece), and entry-level AND section-levelappliesTocompose. RecordsCODESTYLE.md's section→scope mapping (whole-carried, so documented not mechanized).spec/files.schema.json: asectionselement may be a bare string or a{name, appliesTo}object (oneOf). Backward-compatible — every existing entry stays valid.spec/audit.py: matchappliesToagainst a repo's full selector set (repo_selectors), and replace the hardcoded operationaldevelop.jsonpath-swap with two data entries infiles.json(appliesTo: ["release"]vs["operational"]). This is the proof that the selector model subsumes an existing ad-hoc carve-out.spec/validate.py: first pass overfiles.json— everyappliesTotoken must resolve to a known selector, no project type may collide with a reserved token (keeps the namespaces disjoint), and every cataloged repo must declareconsumerModelso apush/pull-scoped section can't fail open.AUDIT.md§3 +files.jsonnote: one-line pointers to the scope model.Inertness — verified
The four selector namespaces are disjoint and no current
appliesTotoken is non-*, so broadening the matcher changes no match. Proven by computing the required-file set for all 21 cataloged repos under old-code+old-data vs new-code+new-data: 0 differences.spec/validate.pystays green (and now also validatesfiles.json).markdownlint,editorconfig-checkerclean.Held on
develop— nodevelop → mainpromotion — per the standing instruction to observe what returns to develop first.