fix(specverb-resolution): exact operationId word-set match should beat a superset (kills needless op pins) #195
Labels
No labels
burndown-2026-06
sunday-sprint
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/cli-guard#195
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug
specverb.resolveOp's operationId fallback (specverb-resolution.md) matches a grant's verb+resource against the words of each operationId (camelCase / kebab / snake split), then applies the plural / least-nested disambiguation and fails closed on a tie. But it treats an exact word-set match and a strict superset match as equal candidates. So a grant that exactly names one operationId still fails closed when another operationId is a superset of its words, forcing a needlessoppin.Repro (skillsmp)
Two ops:
searchSkills(words[search, skills]) andaiSearchSkills(words[ai, search, skills]). Grantcan search skills(words[search, skills]):searchSkills- exact word-set match.aiSearchSkills- superset (contains[search, skills]plusai).Both are kept as candidates → ambiguous → fail-closed → the author is forced to pin
op searchSkills. That is exactly why the skillsmp guardfile (ward.ward/ward-kdl/ward-kdl.skillsmp.guardfile.kdl) carries twooppins the docs otherwise call "rarely needed."Fix
When the grant's word-set matches one operationId exactly (set-equal) and the other candidates match only as supersets, the exact match wins - the same spirit as the existing "prefer plural collection / least-nested path" disambiguation, extended to the operationId fallback. A genuine tie (two exact matches, or two equal-distance supersets) still fails closed and still names the candidates.
Why it matters
opis meant to be a rare override for genuinely ambiguous or machine-garbage operationIds (glitchtip'sapps_projects_api_create_project). An exact match losing to a superset is not that. Tightening this removes a whole class of needless pins and makes verb+resource auto-resolution live up to the "op is rarely needed" promise.Scope
specverb.resolveOp/ the operationId fallback, plus a resolution test with the search / ai-search shape. No grammar change. Affects every spec-backed consumer (the ward-kdl CLI guardfiles today).Related: cli-guard#147 (the convention +
op-override seam). Note: ward-mcp is moving to inline operation authoring (no operationIds at all), so this fix is for the spec-backed CLI path, not ward-mcp.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-cli-guard-195on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-07T07:38:09Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).
run seed context — what this run is carrying (ward#609)
coilyco-flight-deck/cli-guard#195· branchissue-195· driverclaude· workflowdirect-mainengineer-claude-cli-guard-195· wardv0.419.0· dispatched2026-07-07T07:38:09ZIssue body as seeded:
… (truncated to 2000 chars; full body is on this issue)
Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.419.0).
— Claude (she/her), via
ward agentWARD-OUTCOME: done - exact operationId word-set now beats a superset in the specverb fallback; skillsmp
search/ai-searchgrants resolve pin-free.The honest part of this one: the issue's repro was an idealized model, not the actual code path. It described
search skillsreaching the operationId fallback and losing a superset tie. But when I traced the real skillsmp shape (paths/searchand/ai-search, no REST resource segment), thesearchverb early-returned fromsearchCandidatesand never reached the fallback at all, andai-searchdidn't tokenize-match because the predicate compared the verb as a single word. So the stated fix (exact-beats-superset) was necessary but, alone, completely inert for the motivating case.I verified that with a throwaway test against the real spec before writing a line of the fix, which I'm glad I did. The real fix is three interlocking pieces: exact-beats-superset (
keepExact), verb word-splitting soai-searchmatchesaiSearchSkills, and lettingsearchfall through to the fallback on a structural miss. All three are needed for both pins to become removable downstream. Fairly confident: fail-closed semantics are preserved (genuine ties still error and name candidates), and single-word verbs are unchanged since the new path only diverges for multi-word verbs that previously always failed closed anyway.Rough edges / follow-ups: the actual
oppins live in the ward-kdl guardfile, not here, so removing them is a downstream job when ward bumps cli-guard off this tag. Worth a follow-up issue over there to drop the two skillsmp pins and confirm the lock re-resolves. Also spent more time than I'd like squeezing the doc under the 4000-char markdown cap, which nibbled at some unrelated prose.