feat(mentions): naming someone in a reply reaches them #458
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!458
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/naming-someone-reaches-them"
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 #457
The Echo half of #219. Kai activated the intent and said "Lets turn on the feature" at 09:02Z.
Two defects, each sufficient on its own
Parse: []— Discord parsed no mentions, so even a correct<@id>arrived inertThe service could name people and could not reach them. Thirteen hours of discussion treated this as an MCP grant; the Deep half is, and the Echo half never was.
Parse stays empty
The allowance is an explicit list of ids the harness resolved. A mention is something the harness decided to deliver, not something the model wrote — parsing reply text would let a model be talked into pinging
@everyone, which is what the suppression was there for.The roster is the conversation
An author in the history, the member who spoke, or someone one of those messages mentioned. No membership lookup, no API call — it is already in the payloads the turn was built from.
Every guild member and named roles were both considered. This is the narrowest: the only people reachable are people already in the room, and widening later is a change of source rather than of shape. I took that decision rather than escalating it, because the narrow version needs nothing new and answers the case the issue was filed about.
Four bounds, each because the alternative is worse than the feature
alphabetdoes not reachalphaAn empty roster leaves a reply byte-identical, which is every turn until someone is named.
ward exec gategreen: build, policy-check, vet, test, test-skips, pre-commit.Review — Angie (ENG) · s/4b1e. No objection. The safety property is preserved in the way that matters, and I want to say why it is the right way.
Parsestays empty and the allowance is an explicitUserslist of ids the harness resolved. That keeps the original guarantee intact rather than trading it away: a mention is now something the harness decided to deliver, never something the model wrote. Parsing reply text would have been the obvious implementation and would have handed an attacker a route to a channel-wide ping, which is exactly what the emptyParsewas protecting against. Preserving a bound while removing the limitation it caused is harder than either dropping it or keeping the feature out.The roster choice is the second good decision. Authors in history, the member who spoke, and people those messages mentioned — all already in payloads the turn was built from, so no membership lookup and no API call. It is also the narrowest plausible source, and you say that widening to guild members or roles is a change of source rather than of shape, which tells the next person the extension point without inviting them through it.
The four bounds each have a reason and the reasons are about people rather than code. Someone named four times being reached once, because four pings for one sentence is what makes people mute a channel, is the one I would have missed. A three-character floor and a word-boundary rule are the difference between a feature and a nuisance.
Checked, not assumed: the diff shows forty lines removed from
internal/community/sandboxwiring_test.go, which is the wiring test that landed a few minutes ago. The merge result keeps it at forty lines, identical tomain. Stale-branch artifact, not a revert.Merges clean.
One thing worth a follow-up rather than a change here. The roster is built from the turn's payloads, which include member-authored text. A member can therefore influence who is mentionable by naming someone in their own message. That is correct for the feature — reaching a person the conversation is about is the point — and it is also a small surface worth knowing exists: the set of reachable accounts is member-influenced, even though the decision to reach one is the harness's. Nothing to fix today; worth stating in the doc so the next person widening the roster knows they are widening an attacker-influenceable set.