feat(opcore) [PR3/3 of #196]: inline-operation source (ParseInline) for the ward-mcp .mcp.kdl grammar #199
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#199
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?
Scope (PR3 of the cli-guard#196 split - the inline-operation source)
Add a second
Descriptorsource that states the descriptor from inline KDL instead of resolving it against an OpenAPI spec, mirroring howexecverb.Parse(cli/execverb/guardfile.go:155) authors exec commands inline with no spec. Blocked on PR1. Co-designs with ward-mcp#6 (the grammar).Lives in
http/opcore(urfave/cli-free) - not in specverb, or it would re-couple MCP to the CLI projection.Add to
http/opcoreParses the ward-mcp#6 grammar, frozen as
can <verb> <resource> { path "..."; query "..."; body "..."; set k=v }:github.com/calico32/kdl-go, same node-walking shape asguardfile.parseGrant/execverb.parseGrant.opcore.MethodForVerb(extracted fromverbConventionsin PR1): create→POST, get/view/list/search→GET, edit→PATCH/PUT, delete→DELETE, the state toggles and membership verbs already enumerated inverbConventions.{template}via the existingpathParamsInOrder(path)/pathParamRe(http/specverb/request.go:303region) - no operationId, noopresolution.query "a" "b"→Field{Location: query};body "x" "y"→ bodyFields;set state="closed"→FixedBody(the KDL-nativeRawValue()path,http/guardfile/guardfile.go:688).base-url+auth <scheme> { value env <VAR> }+restrict <param> matches <glob>into theRuntimeConfigthat builds anopcore.Runtime(the auth env-value provider, base-url, restrictions).checkFlagCollisions(http/specverb/specverb.go:433) so an inline descriptor fails closed on a reserved-flag clash exactly like a resolved one.Output is a slice of the same
opcore.Descriptorthe OpenAPI source feeds, so every downstream projection is source-blind. Upstream-agnostic: the KDL carries generic HTTP-op structure, no vendor strings, no operationIds.Done
ParseInlinein opcore, with tests over the frozen ward-mcp#6 grammar: method-from-verb, path-params-from-template,set→FixedBody,auth/restrict/base-url→ RuntimeConfig, and reserved-flag-collision fail-closed.Descriptortype feeds both sources (a shared golden: an inline op and an equivalent resolved op produce equal descriptors).ward vet/ward testgreen.Related
cli-guard#196 (design), PR1 (the extraction +
MethodForVerb), ward-mcp#6 (the grammar this parses), ward-mcp#7 (the runtime that calls ParseInline + Execute).🔒 Reserved by
ward agent --driver claude— containerengineer-claude-cli-guard-199on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-07T09:08:03Z). 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#199· branchissue-199· driverclaude· workflowdirect-mainengineer-claude-cli-guard-199· wardv0.419.0· dispatched2026-07-07T09:08:03ZIssue 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 - opcore.ParseInline lands as the second Descriptor source (inline ward-mcp KDL), byte-for-byte equal to a resolved op.
This one went smoothly, largely because PR1 had already carved out the right seams: MethodForVerb was sitting in opcore waiting, and pathParamRe was already there in the binder, so the inline parser was mostly assembling parts that existed. The satisfying moment was the shared golden going green on the first run - an inline
get repoand a spec-resolvedget repocompared with reflect.DeepEqual and matched exactly, which is the real proof that both sources feed one source-blind type.A couple of things fought back, none deep. The layering meant
checkFlagCollisions,reservedFlagNames, anddestructiveVerbshad to move down from specverb into opcore (specverb can't be imported upward), so I aliased them back so specverb's call sites and tests stayed put. The base-url refactor bit me once: my first ParseBaseURL clobbered the string field with an empty when a block node followed, which silently killed guardfile's existing two-forms fail-closed test - a good reminder that the accumulate-then-validate shape was load-bearing. And then a long tail of pre-commit comment-cap and the 4000-char FEATURES.md cap, which cost more edits than the actual code.Confidence is high: engine stays urfave/cli-free, the fail-closed surface is well-tested, and the golden pins the contract. Rough edges worth a follow-up: inline query/body fields are all typed
string(no schema to draw from), so if ward-mcp ever needs typed inline inputs that's a grammar extension; and only thecanmodal is accepted inline (no cannot/never), which is right for MCP but worth stating if the grammar grows. Reused guardfile's auth/restrict parsers directly, so auth semantics can't drift between the two sources.