ok — decode, validation and transport all handle 20
1 ticket targeting trucker-ecommerce
refused
MCP error -32000: capture refused by I1 sovereignty gate:
effective classification confidential through us-nexus origin
infra#265 tried 20, then 5, both targeting a client repo, and never tried 1. Length was never the variable — the repo classification was. trucker-ecommerce is absent from classification.yaml, which means confidential by fail-safe default, and the caller was on claude.ai (a us-nexus origin). The gate did exactly its job.
Why it read as an array bug
The refusal named neither the offending target nor that a policy had said no. With twenty tickets in one call, "which of these tripped it?" is unanswerable from that message — so "the array is broken" is a reasonable conclusion from the evidence available. The cost was three wasted round trips, a 20-call fallback, and two issues filed against the wrong component (including a repo-split proposal).
That is the defect this PR fixes.resolveClassification now also returns the targets carrying the effective level:
... through us-nexus origin (required by: trucker-ecommerce)
Two deliberate choices:
Only targets that reach the effective level are named. At twenty tickets, listing the innocent ones hides the one that matters.
A caller who declared confidential themselves is told exactly that (declared by the caller; no target required it), because "you asked for this" and "a repo you touched required it" need different fixes.
Tests
TicketsArrayWorksAtLengthOne / ...AtLengthMany (20) — the regression tests #91 asks for, pinning a path that was already correct
OneFailingTicketDoesNotSinkTheRest — infra#265 asked "if ticket 3 of 5 fails, what happened to 1 and 2?" They land; the receipt says so per item; the call is not an error
I1RefusalNamesWhatEscalatedTheClassification — the red test for this change
task check green.
What I did not do
Did not strip tickets from the schema. The instruction was to fix the array path or remove the field, on the premise that it always fails. It does not, so removing it would delete a working capability and leave the actual defect — an unactionable refusal — in place.
Did not touch the gate's behaviour. The refusal is correct: client data must not flow to a us-nexus surface. Only the message changed.
Follow-on worth considering separately
A caller hitting this legitimately (filing a client-repo backlog) has no path except switching harness. Whether a sovereign-origin principal should be able to file client tickets through capture is a policy question, not a message one — say the word and I'll file it.
Closes the part-1 half of #91. **The `tickets` array was never broken** — the bug is in what the refusal says.
## Reproduction, live against brain-mcp
| probe | result |
|---|---|
| 1 ticket, `comment`, `hyperguild`, dry run | ok |
| 1 ticket, `comment`, `hyperguild`, **real write** | ok — [comment 6356](https://git.d-ma.be/mathias/hyperguild/issues/91#issuecomment-6356) |
| 2 tickets, `create` + `comment`, real write | ok — created #94 and [comment 6358](https://git.d-ma.be/mathias/hyperguild/issues/91#issuecomment-6358) in one call |
| 20 tickets, `create`, dry run | ok — decode, validation and transport all handle 20 |
| **1 ticket targeting `trucker-ecommerce`** | **refused** |
```
MCP error -32000: capture refused by I1 sovereignty gate:
effective classification confidential through us-nexus origin
```
infra#265 tried 20, then 5, both targeting a client repo, and never tried 1. Length was never the variable — the **repo classification** was. `trucker-ecommerce` is absent from `classification.yaml`, which means confidential by fail-safe default, and the caller was on claude.ai (a us-nexus origin). The gate did exactly its job.
## Why it read as an array bug
The refusal named neither the offending target nor that a policy had said no. With twenty tickets in one call, "which of these tripped it?" is unanswerable from that message — so "the array is broken" is a reasonable conclusion from the evidence available. The cost was three wasted round trips, a 20-call fallback, and two issues filed against the wrong component (including a repo-split proposal).
**That is the defect this PR fixes.** `resolveClassification` now also returns the targets carrying the effective level:
```
... through us-nexus origin (required by: trucker-ecommerce)
```
Two deliberate choices:
- **Only targets that reach the effective level are named.** At twenty tickets, listing the innocent ones hides the one that matters.
- **A caller who declared `confidential` themselves is told exactly that** (`declared by the caller; no target required it`), because "you asked for this" and "a repo you touched required it" need different fixes.
## Tests
- `TicketsArrayWorksAtLengthOne` / `...AtLengthMany` (20) — the regression tests #91 asks for, pinning a path that was already correct
- `OneFailingTicketDoesNotSinkTheRest` — infra#265 asked "if ticket 3 of 5 fails, what happened to 1 and 2?" They land; the receipt says so per item; the call is not an error
- `I1RefusalNamesWhatEscalatedTheClassification` — the red test for this change
`task check` green.
## What I did not do
**Did not strip `tickets` from the schema.** The instruction was to fix the array path *or* remove the field, on the premise that it always fails. It does not, so removing it would delete a working capability and leave the actual defect — an unactionable refusal — in place.
**Did not touch the gate's behaviour.** The refusal is correct: client data must not flow to a us-nexus surface. Only the message changed.
## Follow-on worth considering separately
A caller hitting this legitimately (filing a client-repo backlog) has no path except switching harness. Whether a sovereign-origin principal should be able to file client tickets through capture is a policy question, not a message one — say the word and I'll file it.
infra#265 reported "capture with a tickets array fails": 20 tickets
errored, 5 errored, the caller fell back to 20 individual issue_create
calls and filed a bug against the array.
The array was never the failing dimension. Every ticket in that batch
targeted a client repo, and the I1 sovereignty gate refuses a
confidential capture through a us-nexus origin at ANY length — reproduced
live today against brain-mcp: length 1 succeeds for an internal repo,
length 20 validates clean, and a single ticket targeting
trucker-ecommerce is refused.
What made it look like an array bug is that the refusal named neither the
offending target nor the fact that a policy said no:
capture refused by I1 sovereignty gate: effective classification
confidential through us-nexus origin
With twenty tickets a caller cannot tell which repo tripped it, so
"the array is broken" is a reasonable conclusion from the evidence
available. resolveClassification now also returns the targets carrying
the effective level, and the refusal names them:
... through us-nexus origin (required by: trucker-ecommerce)
Only targets that actually reach the effective level are named — at
twenty tickets, listing the innocent ones hides the one that matters —
and a caller who declared confidential themselves is told exactly that,
because "you asked for this" and "a repo you touched required it" call
for different fixes.
Regression tests at length 1 and 20 pin the array path that was never
broken, and a third pins per-item partial success: infra#265 asked "if
ticket 3 of 5 fails, what happened to 1 and 2?" — they land, and the
receipt says so per item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
mathias
merged commit eb9e3e69a9 into main2026-08-18 18:13:48 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the part-1 half of #91. The
ticketsarray was never broken — the bug is in what the refusal says.Reproduction, live against brain-mcp
comment,hyperguild, dry runcomment,hyperguild, real writecreate+comment, real writecreate, dry runtrucker-ecommerceinfra#265 tried 20, then 5, both targeting a client repo, and never tried 1. Length was never the variable — the repo classification was.
trucker-ecommerceis absent fromclassification.yaml, which means confidential by fail-safe default, and the caller was on claude.ai (a us-nexus origin). The gate did exactly its job.Why it read as an array bug
The refusal named neither the offending target nor that a policy had said no. With twenty tickets in one call, "which of these tripped it?" is unanswerable from that message — so "the array is broken" is a reasonable conclusion from the evidence available. The cost was three wasted round trips, a 20-call fallback, and two issues filed against the wrong component (including a repo-split proposal).
That is the defect this PR fixes.
resolveClassificationnow also returns the targets carrying the effective level:Two deliberate choices:
confidentialthemselves is told exactly that (declared by the caller; no target required it), because "you asked for this" and "a repo you touched required it" need different fixes.Tests
TicketsArrayWorksAtLengthOne/...AtLengthMany(20) — the regression tests #91 asks for, pinning a path that was already correctOneFailingTicketDoesNotSinkTheRest— infra#265 asked "if ticket 3 of 5 fails, what happened to 1 and 2?" They land; the receipt says so per item; the call is not an errorI1RefusalNamesWhatEscalatedTheClassification— the red test for this changetask checkgreen.What I did not do
Did not strip
ticketsfrom the schema. The instruction was to fix the array path or remove the field, on the premise that it always fails. It does not, so removing it would delete a working capability and leave the actual defect — an unactionable refusal — in place.Did not touch the gate's behaviour. The refusal is correct: client data must not flow to a us-nexus surface. Only the message changed.
Follow-on worth considering separately
A caller hitting this legitimately (filing a client-repo backlog) has no path except switching harness. Whether a sovereign-origin principal should be able to file client tickets through capture is a policy question, not a message one — say the word and I'll file it.
infra#265 reported "capture with a tickets array fails": 20 tickets errored, 5 errored, the caller fell back to 20 individual issue_create calls and filed a bug against the array. The array was never the failing dimension. Every ticket in that batch targeted a client repo, and the I1 sovereignty gate refuses a confidential capture through a us-nexus origin at ANY length — reproduced live today against brain-mcp: length 1 succeeds for an internal repo, length 20 validates clean, and a single ticket targeting trucker-ecommerce is refused. What made it look like an array bug is that the refusal named neither the offending target nor the fact that a policy said no: capture refused by I1 sovereignty gate: effective classification confidential through us-nexus origin With twenty tickets a caller cannot tell which repo tripped it, so "the array is broken" is a reasonable conclusion from the evidence available. resolveClassification now also returns the targets carrying the effective level, and the refusal names them: ... through us-nexus origin (required by: trucker-ecommerce) Only targets that actually reach the effective level are named — at twenty tickets, listing the innocent ones hides the one that matters — and a caller who declared confidential themselves is told exactly that, because "you asked for this" and "a repo you touched required it" call for different fixes. Regression tests at length 1 and 20 pin the array path that was never broken, and a third pins per-item partial success: infra#265 asked "if ticket 3 of 5 fails, what happened to 1 and 2?" — they land, and the receipt says so per item. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m