feat(opcore): let a mapped body carry pinned constants #313
No reviewers
Labels
No labels
burndown-2026-06
sunday-sprint
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/umbra!313
Loading…
Reference in a new issue
No description provided.
Delete branch "task/mapped-body-pins"
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?
Closes #311.
The issue's analysis was right, and its fix was not sufficient
The diagnosis in the issue and its follow-up comment both hold up:
mapisthe only construct that renames,
setrefused to combine with it, and beavercannot absorb this the way
querypin.goabsorbed the query-side case, becauseprojectMappedBodyrebuilds the body from the mappings alone and dropsanything seeded into
args.Body.So this takes the smaller of the two proposals.
setseeds the map themappings project onto, and a key that is both pinned and mapped fails closed
rather than picking a silent winner. The
BodyFlagsprohibition stays: twomodel-supplied values colliding is a real ambiguity, a pin and a mapping is not.
But that alone does not unblock the consumer. The issue expected the
guardfile to then carry
set contents={text:#true}beside itsmap. Thatsyntax does not exist: a KDL property holds a scalar, and the parameter that
motivated this takes an object. Verified against the parser -
So
setalso gains a block form: one node per key, one argument, several foran array, or a nested block for an object, nesting as deep as the upstream
needs. This is the issue's option 2 in substance, but spelled with the word the
engine already has rather than a third pin mechanism, which is what the
follow-up comment argued for.
The Exa shape now works end to end, asserted through the parser and onto the
wire in
TestInlineMappedBodyWithPinnedObjectReachesTheWire:A caller naming
contentsornumResultsdoes not reach them, and neither keyappears in the input schema. That is the property the construct exists for.
Two hand-rolled body assemblers folded in
assembleBodywas one of three places that ordered the body modes. The othertwo short-circuited on
FixedBody, so withset+maplegal they would havesent the pins without the mapped keys:
opcore.AssembleBody, so a preview cannot disagree with the request it previewsrequest.gobuilds flags fromBodyFlagsonly), so that surface can never fill one, and refusing beats sending a partial body. Pre-existing: a mapped-body grant already sent an empty body there.A test that was passing on nothing
TestParseInlineBodyMappingsRejectOtherBodyModes/fixed_bodyasserted exactlythe rule this PR changes, and kept passing after I removed the rule. It was
failing on a KDL lex error, a bare
trueneeding to be#true, so it hadnever tested the body-mode rejection at all.
It is replaced by the pinned-key/map-target collision case, and the new
fail-closed table asserts the error is not a
parse KDLerror, so thisparticular way of passing on nothing cannot recur.
Verification
mainwithfixed body and body mappings cannot be combined (fail-closed), the error quoted in the issue.make godoc-updateregeneratedgodoc-current.txtfor the new exportedAssembleBody.go build,go vet,go test ./...,golangci-lint run ./..., andpre-commit run --all-filesare green.One note on docs
docs/opcore-inline.mdwas at 3993 chars against a 4000 cap, so the newconstruct did not fit. Body projection and pins moved to
docs/opcore-body.md, which is what the hook's own message advises and leavesthe grammar page at 3896. Nothing was dropped, only relocated and linked from
FEATURES.md.Downstream
coilyco-bridge/deploy#741is correcting the consumer comment. Once thislands, the "one-line change" claim it retracts becomes true again in the
set-beside-mapform, and the guardfile can pincontentswithout openinga model-facing input.
A guardfile needing both a renamed input and a fixed upstream parameter could express neither together. `map` is the only construct that renames, and it refused to combine with `set`, so the operator's choice was to hand the key to the model or leave it absent. That is the worst of the three outcomes for a parameter family that drives cost. `set` now seeds the body a mapping projects. The two do not conflict: projectMappedBody builds a fresh map, so pins seed it before the mappings project onto it. A key that is both pinned and mapped fails closed rather than picking a silent winner. The body-fields prohibition is untouched, that one being a real ambiguity between two model-supplied values. Pinning alone was not enough to unblock the case. A KDL property holds a scalar, so `set contents={text:#true}` is not expressible and the parameter that motivated this takes an object. `set` therefore gains a block form: one node per key, one argument, several for an array, or a nested block for an object. A pinned key still never enters the input schema, which is the property separating a pin from a field. Two hand-rolled body assemblers are folded into the engine so they cannot order the modes differently: the action dry-run planner now previews what it would send, and the CLI refuses a mapped-body grant instead of sending a body missing its mapped keys, mapped sources mounting no CLI flag. TestParseInlineBodyMappingsRejectOtherBodyModes/fixed_body asserted the rule this changes and passed on a KDL syntax error rather than on the rule, a bare `true` lexing as an identifier. It is replaced by the collision case, which fails for the reason it names. Closes #311 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>