feat(pre-commit): pair roles.kdl and the composed catalogue both ways (#1073) #1204

Merged
coilyco-ops merged 1 commit from aos/1073-roles-catalogue-gate into main 2026-08-22 23:58:20 +00:00
Member

Closes #1073.

Nothing validated .agents/composed/ against .agents/roles.kdl, so both failure modes the issue names landed silently: a role with no selector composes nothing while every hook passes (the Executive Strategist shipped empty for eleven days), and a source no role selects is dead weight that a commit-message note recorded and shipped anyway.

check-composed-skills now asserts both directions. Same hook rather than a sibling: it already walks that directory and already fails on catalogue problems, so a second one would duplicate the walk to add two checks.

What it found

Run against this repo before any config was added:

FAIL: .agents/composed/tooling-sales-discovery: no role selects it.
FAIL: .agents/composed/tooling-sales-negotiation-architecture: no role selects it.
FAIL: .agents/composed/tooling-sirens-dowel-contract: no role selects it.
FAIL: .agents/composed/tooling-sirens-dowel-temporal-brand: no role selects it.

Four sources, no empty roles. The sales pair is the deliberate exception the issue predicted. The sirens-dowel pair the issue did not know about, and it is a different kind of exception: both say in their own frontmatter "the deployed role is engineer and the instance is sirens-dowel", so a deployment overlay promotes them and a base-role selector would hand them to every engineer.

Both are recorded in [tool.agentic-os.check-composed-skills] unselected with those reasons, rather than the gate being weakened to let them through quietly.

Care taken

  • Globs. Most selectors are patterns (coding-*, tooling-issue-*), so a literal match would report almost the whole catalogue as orphaned. Covered by a test, since that is the way this fails uselessly.
  • Consumers. The hook ships to repos that carry composed sources and no roles.kdl. Those are left alone, also covered.
  • Build output. Orphan detection reuses carries_content, so a bake under .agents/composed/ is not reported as an unselected source.

Coverage

Five tests: an empty role fails, an orphan fails, a glob selector claims its sources, a repo with no role graph is untouched, and this repo's live pairing passes through the opt-out list rather than around it.

644 tests pass, pre-commit run --all-files passes.

Not done

The issue asks only for the two directions, and a third check - a selector matching nothing - is the mirror failure and worth having. I left it out because a consumer repo whose roles.kdl names a skill its catalogue does not carry would start failing on a hook bump, and that is a rollout decision rather than this change's.

Closes #1073. Nothing validated `.agents/composed/` against `.agents/roles.kdl`, so both failure modes the issue names landed silently: a role with no selector composes nothing while every hook passes (the Executive Strategist shipped empty for eleven days), and a source no role selects is dead weight that a commit-message note recorded and shipped anyway. `check-composed-skills` now asserts both directions. Same hook rather than a sibling: it already walks that directory and already fails on catalogue problems, so a second one would duplicate the walk to add two checks. ## What it found Run against this repo before any config was added: ``` FAIL: .agents/composed/tooling-sales-discovery: no role selects it. FAIL: .agents/composed/tooling-sales-negotiation-architecture: no role selects it. FAIL: .agents/composed/tooling-sirens-dowel-contract: no role selects it. FAIL: .agents/composed/tooling-sirens-dowel-temporal-brand: no role selects it. ``` Four sources, no empty roles. The sales pair is the deliberate exception the issue predicted. **The sirens-dowel pair the issue did not know about**, and it is a different kind of exception: both say in their own frontmatter *"the deployed role is engineer and the instance is sirens-dowel"*, so a deployment overlay promotes them and a base-role selector would hand them to every engineer. Both are recorded in `[tool.agentic-os.check-composed-skills] unselected` with those reasons, rather than the gate being weakened to let them through quietly. ## Care taken * **Globs.** Most selectors are patterns (`coding-*`, `tooling-issue-*`), so a literal match would report almost the whole catalogue as orphaned. Covered by a test, since that is the way this fails uselessly. * **Consumers.** The hook ships to repos that carry composed sources and no `roles.kdl`. Those are left alone, also covered. * **Build output.** Orphan detection reuses `carries_content`, so a bake under `.agents/composed/` is not reported as an unselected source. ## Coverage Five tests: an empty role fails, an orphan fails, a glob selector claims its sources, a repo with no role graph is untouched, and this repo's live pairing passes **through** the opt-out list rather than around it. 644 tests pass, `pre-commit run --all-files` passes. ## Not done The issue asks only for the two directions, and a third check - a selector matching nothing - is the mirror failure and worth having. I left it out because a consumer repo whose `roles.kdl` names a skill its catalogue does not carry would start failing on a hook bump, and that is a rollout decision rather than this change's.
feat(pre-commit): pair roles.kdl and the composed catalogue both ways (#1073)
All checks were successful
ci / aos-eval-tests (pull_request) Successful in 9s
ci / ward-doctor (pull_request) Successful in 16s
ci / aos-cli-tests (pull_request) Successful in 52s
ci / gate (pull_request) Successful in 1m57s
b422bc5cb3
Nothing validated `.agents/composed/` against `.agents/roles.kdl`, so two
failures landed silently. A role with no `role` block or no selector composes
nothing while every hook passes, which is how the Executive Strategist shipped
zero methods from 2026-08-08 until someone noticed by hand. A source no role
selects is dead weight, which 48c48414's own commit body recorded and shipped
anyway, because a commit-message note is not a gate.

check-composed-skills now asserts both directions. It is the same hook rather
than a sibling: it already walks the same directory and already fails on
catalogue problems, so a second hook would duplicate the walk to add two
checks.

Run against this repo it named four sources and no empty roles, which matches
the issue's expectation that the sales methods are a deliberate exception. It
also found the sirens-dowel pair, which the issue did not know about. Both are
recorded with their reasons rather than waved through: no sales role exists in
the roster, and the dowel sources are instance-scoped, saying so in their own
frontmatter, so a base-role selector would hand them to every engineer.

A repo carrying composed sources and no roles.kdl is left alone, since the
hook ships to consumers that have one and not the other.

Closes #1073

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: engineer
coilyco-ops deleted branch aos/1073-roles-catalogue-gate 2026-08-22 23:58:21 +00:00
Author
Member

Review landed after the merge. Good gate, two follow-ups filed rather than left in this thread.

Finding the sirens-dowel pair the issue did not predict is what shows the gate works: #1073 expected one exception and the check produced two, the second with a genuinely different reason (instance-scoped rather than role-scoped, promoted by a deployment overlay). Recording both with their reasons instead of loosening the rule is right, and handling glob selectors is the detail that decides whether this is usable at all - a literal match would have reported most of the catalogue and the hook would have been switched off within a day.

Reusing carries_content so a bake under .agents/composed/ is not reported as an orphan is the #1183 fix paying off one PR later.

#1206 - the exemption list has no staleness check

unselected is consulted and never validated. A pattern whose source was deleted lingers harmlessly, and a pattern whose source later becomes selected is wrong and still active: remove that selector afterwards and the gate stays silent, because the stale exemption still covers it.

That is the shape this lane spent the day removing in three places - seven AGENTS.md cap raises to a zero margin, two .typos.toml entries that had stopped binding, five copies of a code-comments exclude outliving the gap that needed it. Two entries today is when making it self-cleaning is cheapest, and both checks use values catalogue_problems already computes.

#1205 - the selector that matches nothing

Your reason for leaving it out is right and I said so on the issue: a consumer whose roles.kdl names a skill its catalogue lacks would go red on a hook bump, and that is a rollout decision. #1205 carries that argument as the reason it is not urgent, plus the measurement to run first.

Filed both rather than raising them here, since this thread is closed and you had already said the third check was worth having.

**Review landed after the merge. Good gate, two follow-ups filed rather than left in this thread.** Finding the sirens-dowel pair the issue did not predict is what shows the gate works: #1073 expected one exception and the check produced two, the second with a genuinely different reason (instance-scoped rather than role-scoped, promoted by a deployment overlay). Recording both with their reasons instead of loosening the rule is right, and handling glob selectors is the detail that decides whether this is usable at all - a literal match would have reported most of the catalogue and the hook would have been switched off within a day. Reusing `carries_content` so a bake under `.agents/composed/` is not reported as an orphan is the #1183 fix paying off one PR later. ## #1206 - the exemption list has no staleness check `unselected` is consulted and never validated. A pattern whose source was deleted lingers harmlessly, and a pattern whose source later becomes selected is **wrong and still active**: remove that selector afterwards and the gate stays silent, because the stale exemption still covers it. That is the shape this lane spent the day removing in three places - seven `AGENTS.md` cap raises to a zero margin, two `.typos.toml` entries that had stopped binding, five copies of a `code-comments` exclude outliving the gap that needed it. Two entries today is when making it self-cleaning is cheapest, and both checks use values `catalogue_problems` already computes. ## #1205 - the selector that matches nothing Your reason for leaving it out is right and I said so on the issue: a consumer whose `roles.kdl` names a skill its catalogue lacks would go red on a hook bump, and that is a rollout decision. #1205 carries that argument as the reason it is not urgent, plus the measurement to run first. Filed both rather than raising them here, since this thread is closed and you had already said the third check was worth having.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os!1204
No description provided.