Skip to content

feat(capability): relocate the tool-grant grammar from hermiq (ADR-099 §5) - #2868

Merged
rubenvdlinde merged 3 commits into
developmentfrom
feat/capability-grant-grammar
Aug 26, 2026
Merged

feat(capability): relocate the tool-grant grammar from hermiq (ADR-099 §5)#2868
rubenvdlinde merged 3 commits into
developmentfrom
feat/capability-grant-grammar

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

The OpenRegister half of a PR pair. hermiq's companion repoints its consumers and deletes the originals; it cannot merge until a build carrying this ships.

Two axes, never merged

ADR-099 §5 keeps them apart deliberately:

axis question where
Delegation may principal P act as user B lib/Service/Delegation/ (new, #2864)
Capability may agent X use tool T lib/Service/Capability/this PR

The capability system is not agent-specific and does not belong to hermiq. It resolves against ToolRegistryFacade, which already lives here, so it moves to sit beside what it resolves against.

🔴 Named Capability, never Grant. Once both axes live in one codebase the conflation risk rises rather than falls, and a user approving a tool must not be able to widen whose identity the agent wears. The directory name is the control.

🔴 A relocation with its tests, not a rewrite

Nothing below the docblock changed except the namespace. The five classes arrived by cp and were edited only where the namespace, @package, @covers and the class title named hermiq.

That constraint is not stylistic. The codec carries a measured scar — 35 of 87 tools parsed wrong — and ADR-095's persistence constraint. A rewrite-while-moving reopens both, and the ADR says so in as many words.

⚠️ Four of five tests moved

ToolGrantResolverTest did not, and that is deliberate rather than an omission.

It builds its catalog from hermiq's real tool providers — HermiqToolProvider, MemoryService, MailReadService, NcNativeWriteService, WebFetchService, CourseRecommendationEngine, DelegationService, ToolAccessRequestService, WebSearchClient. It is an integration test against a catalog, not a unit test of the grammar. It stays where the catalog is, repointed at this namespace in the companion PR.

Moving it would have meant rewriting its fixtures — the one thing this relocation must not do.

What stays in hermiq

The Agent.tools binding, per ADR-099 §5. This PR moves the grammar that reads it, not the field.

Verification

tests/Unit/Service/Capability 58 passed, 153 assertions — the same assertions that were green in hermiq
phpcs (--warning-severity=0) clean
phpmd (both rulesets, baselined) clean
psalm no errors
phpstan no errors

That the assertion count carried over unchanged is the point of the check: a relocation that quietly dropped a case would show up here as a smaller number, not as a failure.

🤖 Generated with Claude Code

…9 §5)

The OpenRegister half of the pair. hermiq's companion PR repoints its consumers
and deletes the originals; it cannot merge until a build carrying this ships.

ADR-099 §5 keeps TWO grant axes and forbids merging them:

  * DELEGATION — may principal P act as user B. New, built in lib/Service/Delegation.
  * CAPABILITY — may agent X use tool T. Mature, and the subject of this commit.

The capability system is not agent-specific and does not belong to hermiq. It
resolves against `ToolRegistryFacade`, which already lives here, so it moves to
sit beside what it resolves against.

🔴 A RELOCATION WITH ITS TESTS, NOT A REWRITE. Nothing below the docblock
changed except the namespace. That codec carries a measured scar — 35 of 87
tools parsed wrong — and ADR-095's persistence constraint, and a
rewrite-while-moving reopens both. The five classes arrived by `cp` and were
edited only where the namespace, the @Package tag, the @Covers tag and the class
TITLE named hermiq.

🔴 NAMED `Capability`, NEVER `Grant`. Once both axes live in one codebase the
conflation risk rises rather than falls, and a user approving a tool must not be
able to widen whose identity the agent wears. The directory name is the control.

⚠️ FOUR OF FIVE TESTS MOVED. `ToolGrantResolverTest` did NOT, and that is
deliberate rather than an omission: it builds its catalog from hermiq's real
tool providers — HermiqToolProvider, MemoryService, MailReadService,
NcNativeWriteService, WebFetchService and four more — so it is an integration
test against a catalog, not a unit test of the grammar. It stays where the
catalog is, repointed at this namespace in the companion PR. Moving it would
have meant rewriting its fixtures, which is the one thing this relocation must
not do.

Verified: 58 tests, 153 assertions green here — the same assertions that were
green in hermiq. phpcs, phpmd (both rulesets), psalm and phpstan clean.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 4f2e7b2

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
format
check-schema-l10n
composer ✅ 175/175
npm ✅ 545/545
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-26 11:16 UTC

Download the full PDF report from the workflow artifacts.

The relocation moved five classes and left all 51 of their `@spec` tags pointing
at hermiq's openspec tree. Gate-46 (spec-anchor-existence) reported 94
unresolved findings — and every one of them was mine: the count matched exactly
when the checker was run over the relocated files alone.

A tag whose target does not exist is worse than no tag. It reads as traceability
and dereferences to nothing, which is the shape this fleet already carries ~300
of from archived changes.

WHAT MOVED WITH THE CODE. Five spec files, copied VERBATIM into
openspec/specs/: agent-capability-reach, agent-tool-governance,
governed-cli-mcp-transport, structured-tool-grants, agent-object-leaf. Verbatim
matters mechanically as well as editorially — the headings ARE the anchors, so
rewording one would break the tags the copy exists to serve.

Three of them had only ever existed as change deltas in hermiq. Two of the
targets were ALREADY DEAD THERE: `agent-capability-reach` and
`agent-tool-governance-and-disclosure` have been archived, so those tags
dereferenced to nothing in hermiq too, and the relocation is what surfaced it.
They now point at promoted specs that exist.

THREE TAGS POINTED AT tasks.md, which is a change artefact that dies on archive
by construction. Those are repointed at the requirement each one implements —
a task number is not a behavioural contract and cannot be one.

Verified with the gate's own checker rather than by inspection: 94 findings
before, 0 after, and 0 across every tracked lib/**/*.php in the repository, so
nothing else was disturbed. 58 tests / 153 assertions still green; phpcs clean;
`npm run check:specs` passes.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 817f24b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
format
check-schema-l10n
composer ✅ 175/175
npm ✅ 545/545
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-26 14:33 UTC

Download the full PDF report from the workflow artifacts.

…lements

The previous commit brought five hermiq specs across whole so every `@spec`
anchor would resolve. That was correct and too broad: ~1,534 lines duplicated
across two repos, most of it describing behaviour hermiq still owns.

MEASURED WHICH REQUIREMENTS BELONG HERE rather than judging by title. For each
`### Requirement:` block, whether any anchor the relocated code cites falls
inside it — computed with BOTH slug rules gate-46 accepts, since they part
company on punctuation inside a word. 14 blocks kept, 18 dropped; 1,534 lines
down to 920.

⚠️ A FIRST MEASUREMENT SAID SOMETHING ELSE, and was wrong. Counting hermiq
citations by requirement SLUG reported zero for every requirement in all five
specs, which would have licensed moving them wholly. Counting by spec PATH
instead reported 11, 14, 10, 15 and 1. The slug pass was silently failing to
match — `writedestructive` against `write-destructive` — so its zeros meant "my
normalisation differs", not "nobody cites this". Two instruments disagreed and
the one that could not be fooled by normalisation won.

So hermiq stays canonical for all five. Its specs are untouched and its ~50
references keep resolving. What is here is a bounded subset, and each file says
so in a banner naming how many requirements were left behind and where.

WHY DUPLICATE AT ALL. A `@spec` tag is dereferenced by gate-46 against the
repository it sits in, so a cross-repo reference is not expressible — an
openregister class citing a hermiq spec resolves to nothing, which is the
~300-dead-tag shape this fleet already carries from archived changes. The
duplication is structural, not an oversight, and it is now bounded to exactly
the requirements the moved code implements.

Verified with the gate's own checker: 0 unresolved anchors after the trim, same
as before it. The trim removed text, not traceability.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ e305f28

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
format
check-schema-l10n
composer ✅ 175/175
npm ✅ 545/545
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-26 22:08 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 1766557 into development Aug 26, 2026
83 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/capability-grant-grammar branch August 26, 2026 22:10
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