test(mcp): hold the tool-result boundary to valid UTF-8 at every offset #276
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
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-gaming/sirens-echo!276
Loading…
Reference in a new issue
No description provided.
Delete branch "test/rune-boundary"
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?
Tests only. Second gap from the coverage sweep that produced #275.
Why this one
runeBoundarybounds a tool result before it re-enters the prompt (mcp.go:493). It had 0% coverage.A boundary landing mid-rune feeds the model invalid UTF-8. That is a bad failure to leave untested, because it does not surface as an error — it surfaces later as a model behaving oddly on a truncated document, which is nearly impossible to trace back.
Exhaustive rather than sampled
The input carries one, two, three, and four byte runes, and every offset from 0 to its length is checked. The whole thing is 17 bytes, so exhausting it is free, and the failure mode is a single byte in the wrong place — exactly what sampling misses.
Three properties, asserted separately because they fail differently:
Also held at 8 KiB of three-byte runes, since that is the size the tool budget actually truncates at and a 17-byte string does not exercise it.
Verified by mutation
Walking forward to the next rune start instead of back:
Caught by the never-exceed property, not the UTF-8 one — forward-walking still yields valid UTF-8, it just returns more than the caller asked for. That is why the three properties are separate assertions rather than one.
Same characterization as #275
A negative limit is returned unchanged, so the caller's slice panics. Not filed, for the same reason: no caller passes one, every site passes a constant, and a latent panic with no reachable path is tracker noise. Written down because a computed budget would find it at runtime.
That makes two truncation helpers with the same latent behaviour —
truncateRuneson the Discord send path and this one on the MCP path. Worth one shared decision if anyone wants to make them total, rather than two separate fixes.runeBoundarygoes 0% → 100%.go vet,gofmt, fullgo test ./...,pre-commit run --filesclean.Quail (QA)