feat(opcore): let a mapped leaf carry a declared type #321
No reviewers
Labels
No labels
burndown-2026-06
burndown-2026-08
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/advocate
role/director
role/exec
role/frontend
role/gamedev
role/human
role/platform
role/qa
role/science
role/sysadmin
state
ambient
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/umbra!321
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/sb46-typed-mapped-leaves"
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 #312. This is its item 3, the full fix, after #320 landed items 1 and 2.
What changes
string, integer, number, boolean, object, array.
items=gives an array's element type, andanytakes each element as supplied, the union rule an empty swaggeritemsschema implies. An absent type is string, which is what every mapped leaf projected before, so nothing existing moves.Two halves, and the second is the one that matters
The type reaches the wire.
mappedStringbecomesmappedValue, socontents={"text": true}arrives as an object rather than being unreachable.A wrong shape is refused before the request fires. That is the whole reason this was worth doing: the upstream's 400 was the first and only notice, and it arrived in production against a metered API. An integer leaf given
1.5is refused too, so a declared integer cannot arrive as one. Tested by counting upstream calls rather than reading an exit code — a refusal that still fires the request is not a refusal.The schema said string too
insertMappingFieldhardcodedType: "string". So a tool advertised a string input for a leaf about to send an object. That is fixed in the same change, because a model reading the schema and a runtime building the body disagreeing is how the next version of this bug would arrive.#320's refusal is superseded, not kept
That PR made
type=on amapa parse error naming the string limit. It named the limit because there was no way past it. There is now, so a declared shape parses.What still fails closed: an unsupported type, an
itemsoutside an array, an unknown property, and a child node.TestMapShapeRefusalNamesTheStringLimitis replaced byTestMapCarriesADeclaredTypeandTestMapTypeFailsClosed.Why now, having said it was not worth doing
The issue said item 3 was "probably not worth doing on the strength of one consumer", and I repeated that when landing #320. Two things changed my read:
opcore.CoerceItemsalready exists, from #317's array work, so array elements reuse the coercion the flag path uses and the two cannot drift about what an array of integers is. The design pass the issue wanted is largely already paid for.coilysiren/inbox#426's exit condition is that each declared boundary binds or refuses. Items 1 and 2 made the limit visible; only item 3 makes the guardfile able to express the constraint at all, which is the umbra half of that epic's thesis.Verification
make test,make vet,make lint(0 issues),godoc-current.txtregenerated. New tests cover the Exa case end to end across five types, seven wrong-shape refusals each asserting the upstream was never called, the six parse cases, and five parse-time rejections.Refs coilysiren/inbox#426
🤖 Generated with Claude Code
mapsilently restricts a guardfile to string-valued upstream parameters and the only signal is a 400 at call time #312