Four open issues look independent and are not. #392 (the guard cannot see MCP I/O or a SKILL.md body load), #393 (directories cannot be labelled without asking someone), #394 (an opaque source cannot carry one sound label), #391 (Codex cannot enforce a read decision) are all consequences of enforcing at the read.
Read-side confinement has to be re-solved for every source type, every tool surface, and every runtime — and it fails at exactly the places that make the product useful.
There is one exit: the reply at listener.ts:419, where redactOutbound already runs. Our code, always executed, runtime-independent.
Design: docs/superpowers/specs/2026-08-06-sink-side-provenance-design.md
Evidence: sink-side provenance · label-creep spike · derived access inheritance
What it is
Add a backstop at the sink, keeping the read guard. Not an inversion — an earlier framing proposed replacing the read check, and the evidence rejects that: in LIO the read-side clearance is exactly what permits relaxing the output channel, and #387 independently argues the read check is the better UX.
It is the floating label / high-water mark construction, and it is the same rule FIDES ships at a different sink — max_allowed_confidentiality on post_comment versus ours on the reply.
It is not content classification. Provenance is recorded fact; classification is the thing already rejected on measurement (Presidio at 0.07 recall).
Why nobody ships it
Seventeen systems surveyed. Every AI-DLP product, AI gateway, and agent-framework output guard decides on the text. No shipping system refuses an output based on which sources were read.
The reason is structural and it is in our favour: in all of them the answer goes to the principal who already owns the data. AgentCall is the one shape where the recipient is not the data owner — the same property that makes every industry fail-open default inapplicable here.
Slices — in order, none skippable
Constraints that are not negotiable
Completeness is claimable over an enumerable set, not an open-ended one — SLSA's own retreat from L4 dependency completeness to "best effort" in v1.0 is the precedent. Cryptography does not close it: tamper-evidence and completeness are different properties. The buildable shape is the Linux audit subsystem's loss counter kept outside the record, and in-toto failing when expected link metadata is missing.
Bash under Codex cannot be recorded. The guard inspects a command string; it structurally cannot know which files a command read. Either a Bash-using run cannot release a reply above the caller's clearance, or the guarantee is documented as not holding for Bash. Silence is not an option — that is the defect #390 just fixed, and it must not reappear in the runtime.
Per-value taint is unavailable. CaMeL gets it by owning a Python interpreter; we observe tool calls, not reasoning. Coarse is defensible (FIDES as shipped is coarse), and Vassena et al. show coarse and fine are equally expressive given a scope-limiting primitive — which we lack. That raises #387's constrained output from nice-to-have to structural.
Cost
Over-refusal has the field's name — label creep — measured at up to 24.5 percentage points for FIDES with enforcement on.
The mitigation is already shipped: buildPrompt names the readable sources, so the sink check is a backstop. A pilot measured 0 out-of-scope reads in 6 calls, with the one deliberately nosy question producing zero tool calls at all. That is n=6 — it bounds the rate at ~40% and no better — and the adversarial case was not tested. ReadSecBench measures up to 85% exfiltration through content-borne instructions in a permitted source. Until slice 5 runs, treat the backstop as load-bearing.
Related
#391 · #392 · #393 · #394 · #387 · #173 · #372
Four open issues look independent and are not. #392 (the guard cannot see MCP I/O or a
SKILL.mdbody load), #393 (directories cannot be labelled without asking someone), #394 (an opaque source cannot carry one sound label), #391 (Codex cannot enforce a read decision) are all consequences of enforcing at the read.Read-side confinement has to be re-solved for every source type, every tool surface, and every runtime — and it fails at exactly the places that make the product useful.
There is one exit: the reply at
listener.ts:419, whereredactOutboundalready runs. Our code, always executed, runtime-independent.Design:
docs/superpowers/specs/2026-08-06-sink-side-provenance-design.mdEvidence: sink-side provenance · label-creep spike · derived access inheritance
What it is
Add a backstop at the sink, keeping the read guard. Not an inversion — an earlier framing proposed replacing the read check, and the evidence rejects that: in LIO the read-side clearance is exactly what permits relaxing the output channel, and #387 independently argues the read check is the better UX.
It is the floating label / high-water mark construction, and it is the same rule FIDES ships at a different sink —
max_allowed_confidentialityonpost_commentversus ours on the reply.It is not content classification. Provenance is recorded fact; classification is the thing already rejected on measurement (Presidio at 0.07 recall).
Why nobody ships it
Seventeen systems surveyed. Every AI-DLP product, AI gateway, and agent-framework output guard decides on the text. No shipping system refuses an output based on which sources were read.
The reason is structural and it is in our favour: in all of them the answer goes to the principal who already owns the data. AgentCall is the one shape where the recipient is not the data owner — the same property that makes every industry fail-open default inapplicable here.
Slices — in order, none skippable
tools.logcarries every call but only the tool name and verdict (guard.ts:340-342); the resolved path reachescalls.logonly whennoteworthy— a denial or flag (guard.ts:344-355). An allowed read of a permitted file leaves no entry. Needs sequence numbers, a gap marker, and a decision about the record's own sensitivity — it becomes a full inventory of what the owner's agent read, in a file this codebase notes gets pasted into bug reports.onFailure = ALLOWwith no write (guard.ts:283) is the silent fail-open: a failed decide yields an unrecorded call and the sink concludes nothing was touched.listener.ts:419, beforeredactOutbound.Constraints that are not negotiable
Completeness is claimable over an enumerable set, not an open-ended one — SLSA's own retreat from L4 dependency completeness to "best effort" in v1.0 is the precedent. Cryptography does not close it: tamper-evidence and completeness are different properties. The buildable shape is the Linux audit subsystem's loss counter kept outside the record, and
in-totofailing when expected link metadata is missing.Bash under Codex cannot be recorded. The guard inspects a command string; it structurally cannot know which files a command read. Either a Bash-using run cannot release a reply above the caller's clearance, or the guarantee is documented as not holding for Bash. Silence is not an option — that is the defect #390 just fixed, and it must not reappear in the runtime.
Per-value taint is unavailable. CaMeL gets it by owning a Python interpreter; we observe tool calls, not reasoning. Coarse is defensible (FIDES as shipped is coarse), and Vassena et al. show coarse and fine are equally expressive given a scope-limiting primitive — which we lack. That raises #387's constrained output from nice-to-have to structural.
Cost
Over-refusal has the field's name — label creep — measured at up to 24.5 percentage points for FIDES with enforcement on.
The mitigation is already shipped:
buildPromptnames the readable sources, so the sink check is a backstop. A pilot measured 0 out-of-scope reads in 6 calls, with the one deliberately nosy question producing zero tool calls at all. That is n=6 — it bounds the rate at ~40% and no better — and the adversarial case was not tested. ReadSecBench measures up to 85% exfiltration through content-borne instructions in a permitted source. Until slice 5 runs, treat the backstop as load-bearing.Related
#391 · #392 · #393 · #394 · #387 · #173 · #372