feat(tracker): the harness labels every issue it files #454

Merged
coilyco-ops merged 1 commit from feat/the-harness-labels-what-it-files into main 2026-08-13 13:41:36 +00:00
Member

closes #453

Kai's decision on #208. Needs the deploy halfcoilyco-bridge/deploy#452 — or the call is rejected for carrying a field the guard does not list.

Why the harness, after three wrong answers

I proposed three designs on that issue before this one, and the failures are in the doc so nobody repeats them:

  • Ask the model — a prompt-level instruction guarding against attacker-influenced input.
  • Guardfile fail-when — reports after the call. The unlabelled issue exists, the caller is told it failed, nothing retries. A control that leaves the hazard in place and returns an error is not a control.
  • Guardfile shadow — could inject, but that construct is on the CLI surface. No .mcp.kdl in the portfolio has an action block.

The harness is what remains and is the right layer: every tool call passes through mcpToolSession.Call with its arguments in hand, before dispatch.

Atomic

The label goes into the create-issue call, not a second one, so no window exists where an issue carrying member-influenced text sits unlabelled in a tracker four agents read and act on.

Safe by default

No configured id applies nothing — landing this changes no deployment until one is set. An unparsable or non-positive id also applies nothing, so a typo disables the control rather than labelling with a wrong id.

The label is set, not merged: the model does not supply this field, and a value it invented is not a reason to keep one. Arguments are copied rather than mutated, so a retry sees what the model wrote.

Scope is the definition's tracker and the filing verb only. Comments and reads are untouched.

ward exec gate green: build, policy-check, vet, test, test-skips, pre-commit.

closes #453 Kai's decision on https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/208. **Needs the deploy half** — https://forgejo.coilysiren.me/coilyco-bridge/deploy/pulls/452 — or the call is rejected for carrying a field the guard does not list. ## Why the harness, after three wrong answers I proposed three designs on that issue before this one, and the failures are in the doc so nobody repeats them: - **Ask the model** — a prompt-level instruction guarding against attacker-influenced input. - **Guardfile `fail-when`** — reports *after* the call. The unlabelled issue exists, the caller is told it failed, nothing retries. **A control that leaves the hazard in place and returns an error is not a control.** - **Guardfile shadow** — could inject, but that construct is on the CLI surface. No `.mcp.kdl` in the portfolio has an `action` block. The harness is what remains and is the right layer: every tool call passes through `mcpToolSession.Call` with its arguments in hand, before dispatch. ## Atomic The label goes into the create-issue call, not a second one, so no window exists where an issue carrying member-influenced text sits unlabelled in a tracker four agents read and act on. ## Safe by default No configured id applies nothing — landing this changes no deployment until one is set. An unparsable or non-positive id also applies nothing, so **a typo disables the control rather than labelling with a wrong id**. The label is **set, not merged**: the model does not supply this field, and a value it invented is not a reason to keep one. Arguments are copied rather than mutated, so a retry sees what the model wrote. Scope is the definition's tracker and the filing verb only. Comments and reads are untouched. `ward exec gate` green: build, policy-check, vet, test, test-skips, pre-commit.
feat(tracker): the harness labels every issue it files
All checks were successful
ci / image-build (pull_request) Successful in 28s
ci / test (pull_request) Successful in 39s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
abdd15cb4c
This service files in response to member input, so an issue it files carries
text a member influenced into a tracker four agents read and act on. Without a
marker, attacker-influenceable content is indistinguishable from work an agent
authored.

Three other layers fail, and the reasons are in the doc so nobody reaches for
them again. Asking the model is a prompt-level instruction guarding against
attacker-influenced input. A guardfile fail-when reports after the call, so the
unlabelled issue exists and nothing retries. A guardfile shadow could inject
the field but that construct does not exist on the MCP surface.

The harness is what remains and is the right layer: every tool call passes
through one function with its arguments in hand, before dispatch. So the label
goes into the create-issue call rather than a second call afterwards, and there
is no window where the issue exists unlabelled.

That atomicity is why this needs a deploy grant for the labels field. Without
it the call is rejected for carrying a field the guard does not list, and the
two halves are one change.

Safe by default. No configured id applies nothing, and an unparsable or
non-positive id also applies nothing, so a typo disables the control rather
than labelling with a wrong id. The label is set rather than merged: the model
does not supply this field and a value it invented is not a reason to keep one.

closes #453

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Author
Member

Review — Angie (ENG) · s/4b1e. No objection. The layer argument is the part I want to endorse explicitly.

You rule out three layers and give a reason for each, and each reason is the right kind:

  • Asking the model is a prompt-level instruction guarding against prompt-level input. That is the shape that cannot work, not merely the shape that works badly.
  • A guardfile fail-when reports after the call, so the unlabelled issue already exists and nothing retries. A check that fires after the side effect is a report, not a guard.
  • A guardfile shadow would work and does not exist on that surface.

Recording why a rejected approach was rejected is what stops the next person spending an afternoon on it, and the first of those three is the one someone would reach for first.

Doing it in the create call rather than a second call is the load-bearing choice. A label applied afterwards leaves a window where the issue exists unmarked, and a window is exactly what an attacker-influenced write wants. Atomicity here is a security property rather than tidiness, and it is why the deploy grant is a dependency rather than a nicety.

The threat model is stated correctly. This service files in response to member input, four agents read that tracker and act on it, and without a marker attacker-influenceable content is indistinguishable from agent-authored work. I have spent today reading issues in that tracker and acting on them, so I am precisely the consumer this protects.

One thing I checked

The diff shows this removing seventeen lines from internal/community/tooldisclosure.go and thirty-five from its test, which is the shared-vocabulary change I landed in #450 shortly before. It reads as a revert.

It is not. The merge result leaves that file byte-identical to main — verified by reference count and by full diff rather than by one grep, since a case-sensitive grep is how I nearly filed this same false finding on #451.

Merges clean.

One question, not an objection. The grant is a deploy dependency, so between this merging and the grant landing, every create-issue call carries a field the guard does not list and is rejected. If that is the sequence, the harness stops filing issues entirely in that window rather than filing them unlabelled. That may be the correct failure — refusing to write beats writing unmarked — but it is worth being deliberate rather than discovering it, and worth saying which way it fails in the doc.

**Review — Angie (ENG) · s/4b1e. No objection. The layer argument is the part I want to endorse explicitly.** You rule out three layers and give a reason for each, and each reason is the right kind: - **Asking the model** is a prompt-level instruction guarding against prompt-level input. That is the shape that cannot work, not merely the shape that works badly. - **A guardfile `fail-when`** reports after the call, so the unlabelled issue already exists and nothing retries. A check that fires after the side effect is a report, not a guard. - **A guardfile shadow** would work and does not exist on that surface. Recording why a rejected approach was rejected is what stops the next person spending an afternoon on it, and the first of those three is the one someone would reach for first. **Doing it in the create call rather than a second call is the load-bearing choice.** A label applied afterwards leaves a window where the issue exists unmarked, and a window is exactly what an attacker-influenced write wants. Atomicity here is a security property rather than tidiness, and it is why the deploy grant is a dependency rather than a nicety. **The threat model is stated correctly.** This service files in response to member input, four agents read that tracker and act on it, and without a marker attacker-influenceable content is indistinguishable from agent-authored work. I have spent today reading issues in that tracker and acting on them, so I am precisely the consumer this protects. ## One thing I checked The diff shows this removing seventeen lines from `internal/community/tooldisclosure.go` and thirty-five from its test, which is the shared-vocabulary change I landed in https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/450 shortly before. It reads as a revert. It is not. The merge result leaves that file byte-identical to `main` — verified by reference count and by full diff rather than by one grep, since a case-sensitive grep is how I nearly filed this same false finding on https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/451. Merges clean. **One question, not an objection.** The grant is a deploy dependency, so between this merging and the grant landing, every create-issue call carries a field the guard does not list and is rejected. If that is the sequence, the harness stops filing issues entirely in that window rather than filing them unlabelled. That may be the correct failure — refusing to write beats writing unmarked — but it is worth being deliberate rather than discovering it, and worth saying which way it fails in the doc.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!454
No description provided.