From a19789dfb492a2c7a73d8b234dc7a10f774a32e3 Mon Sep 17 00:00:00 2001 From: Wanjun Gu Date: Fri, 11 Sep 2026 15:22:20 -0700 Subject: [PATCH 1/3] test(privacy): census rows for the two capability sites added since CI last ran it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `the_sites_that_decide_how_far_a_caller_reaches_are_exactly_these` is red on main (run 34649064272): two sites were added while rust.yml still ran only `--lib --bins`, so both were green when they merged and neither is described in the census. - `agents/workspace_inspector.rs` 1 -> 2 (added by e60213c8): the `workspace_set_tools` pre-flight in `WorkspaceMutationInspector`, which asks `set_tools_preflight_refusal` whether the change CAN be made before the user is asked to approve it, and so needs the caller's pair to ask the tier gate with. It samples lazily — only once a `set_tools` call is in the batch — and memoises it, and like its neighbour it prefers a capability threaded in and samples only when handed `None`. The row now states that invariant, which is the property this census exists to protect. - `providers/coding_agent/bridge.rs` 1 -> 2 (added by 2f61d274): a second TEST fixture, the module-level `inert_grant_for_test()`. It cannot call `mod tests`' private `test_capability()` and cannot itself live in `mod tests`, being `pub(crate)` for other modules' tests — so the file's "one named helper" intent now costs two occurrences. The row says so; collapsing them is the bridge authors' call. Neither site is wrong, so neither is removed. No production code changes. --- crates/biorouter/tests/privacy_capability.rs | 72 +++++++++++++------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/crates/biorouter/tests/privacy_capability.rs b/crates/biorouter/tests/privacy_capability.rs index c76f3ddb6..9bb20f867 100644 --- a/crates/biorouter/tests/privacy_capability.rs +++ b/crates/biorouter/tests/privacy_capability.rs @@ -136,18 +136,34 @@ const EXPECTED: &[Site] = &[ Site { needle: "CallCapability::sample(", file: "crates/biorouter/src/agents/workspace_inspector.rs", - count: 1, - what: "`WorkspaceCrossingInspector::inspect`, the first-crossing \ - disclosure. A `ToolInspector` runs BEFORE the dispatch that would \ - admit a capability — that is the point of an inspector — so there \ - is none in scope to inherit, and the alternative to sampling here \ - is not inheriting but deciding on `Config::global()`, which is the \ - bug this type exists to prevent. Sampled ONCE per batch rather than \ + count: 2, + what: "TWO inspectors in this file, each deciding for a batch it sees \ + BEFORE the dispatch that would admit a capability — that is the \ + point of an inspector — so neither has one in scope to inherit on \ + the ordinary agent path, and the alternative to sampling is not \ + inheriting but deciding on `Config::global()`, which is the bug \ + this type exists to prevent. \ + (1) `WorkspaceCrossingInspector::inspect_with_pinned_capability`, \ + the first-crossing disclosure, sampled ONCE per batch rather than \ per request, because two calls in one batch must not be able to \ gate on two different models, and only after a cheap name check \ has established that the batch contains a cross-session write at \ all: an ordinary turn must not pay a provider-mutex read for a \ - disclosure that cannot apply to it", + disclosure that cannot apply to it. \ + (2) `WorkspaceMutationInspector::inspect_with_pinned_capability`'s \ + `workspace_set_tools` pre-flight (QA finding F4), which asks \ + `WorkspaceClient::set_tools_preflight_refusal` whether the \ + tool-set change CAN be made before the user is asked to approve \ + it — a card for an impossible change is a request for authority \ + over nothing — and therefore needs the caller's pair to ask the \ + tier gate with. It samples LAZILY, only once a `set_tools` call \ + has been found in the batch, and memoises the result in `sampled`, \ + so an ordinary batch pays no provider-mutex read and two such \ + calls in one batch still gate on one model. \ + ⚠ Both PREFER a capability threaded in and sample only when handed \ + `None`: a bridge grant supplies the pair it fixed at issue time, \ + which is what keeps a bridged coding agent's calls from re-reading \ + the flag on this side of the process boundary", }, Site { needle: "CallCapability::sample(", @@ -280,21 +296,31 @@ const EXPECTED: &[Site] = &[ Site { needle: "CallCapability::public_enforced(", file: "crates/biorouter/src/providers/coding_agent/bridge.rs", - count: 1, - what: "NOT a production decider: `mod tests`' `test_capability()` helper, \ - which every `BridgeGrant` the bridge's own unit tests build takes its \ - pair from — the most restrictive one, rather than a permissive one \ - invented for a test's convenience. Counted for the same reason \ - `privacy/grant.rs`'s test helper is — a line-wise grep cannot tell a \ - `#[cfg(test)]` block from production, and a filter that tried would \ - blind the census to production too. The production side of this \ - bridge decides in `Agent::issue_tool_bridge`, which is a `sample(` \ - row above. ⚠ This row read `dummy_grant()` while four inline \ - spellings had accumulated in that file, so the census sat RED \ - through a whole branch whose own gate list never ran this binary. \ - The single helper is what stops it drifting that way again: a new \ - test that inlines the constructor still moves the count off 1 and \ - still fires here", + count: 2, + what: "NOT a production decider: the bridge's two TEST fixtures, each \ + taking the most restrictive pair rather than a permissive one \ + invented for a test's convenience. (1) `mod tests`' \ + `test_capability()`, which every `BridgeGrant` the bridge's own \ + unit tests build takes its pair from. (2) the module-level \ + `inert_grant_for_test()` — `#[cfg(test)] pub(crate)`, a grant that \ + dispatches nothing and needs no runtime — which tests in OTHER \ + modules of this crate build on. They are two rather than one \ + because a `pub(crate)` fixture cannot live inside `mod tests`, and \ + from outside that module its private `test_capability()` is not \ + visible; funnelling both through one helper at module level would \ + return this row to 1 and is the bridge authors' call, not this \ + census's. Counted for the same reason `privacy/grant.rs`'s test \ + helper is — a line-wise grep cannot tell a `#[cfg(test)]` block \ + from production, and a filter that tried would blind the census to \ + production too. The production side of this bridge decides in \ + `Agent::issue_tool_bridge`, which is a `sample(` row above. \ + ⚠ This row read `dummy_grant()` while four inline spellings had \ + accumulated in that file, so the census sat RED through a whole \ + branch whose own gate list never ran this binary; it then sat RED \ + again from `inert_grant_for_test()`'s arrival until CI began \ + running `tests/*.rs` and anyone saw it. A new test that inlines the \ + constructor rather than calling one of these two still moves the \ + count and still fires here", }, // ---------------------------------------------------------- DR-26's third // axis, asked through the free function rather than off a capability. Its From cdef6bb728d4bb6dbbcb0224e321e6b19211cc8d Mon Sep 17 00:00:00 2001 From: Wanjun Gu Date: Fri, 11 Sep 2026 15:26:07 -0700 Subject: [PATCH 2/3] test(privacy): narrow to the workspace_inspector row; leave bridge.rs alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My first commit also took `providers/coding_agent/bridge.rs` to count 2. That was wrong to ship: #228 is removing the extra site instead (its `inert_grant_for_test()` inlined the constructor; the helper now takes its pair from the file's documented `tests::test_capability()`), which returns that file to one occurrence and leaves the existing row, its count and its prose true. A row of 2 here would turn main red again the moment their fix lands. What remains is one row for one file: `agents/workspace_inspector.rs`, 1 -> 2. Extending the existing row rather than adding a second is forced by the test — `found` is keyed by (needle, file) and summed, `want` is one tuple per row with no aggregation, so two rows for one file fail even at count 1 each. The census mechanism is untouched. The row now states what I read in the code rather than what the pre-flight is called: both inspectors funnel into `inspect_with_pinned_capability`, whose `let mut sampled = capability` uses a pinned pair as is and never reads the provider mutex (only the coding-agent bridge pins one; the agent loop and the approval relay pass None); the sample runs lazily inside the `is_set_tools_call` arm and memoises, so a batch with no such call samples nothing; and the pair is handed to `preflight_set_tools`, which asks the §7 write row, Gate F reach for added extensions, the removals' manageability refusal and `bind_allowed` for a provider switch. It is not the gate — `handle_set_tools` re-runs the same pre-flight against the admitted capability, so a stale sample fails safe. On this branch alone the census still fails, with exactly the bridge.rs delta that #228's PR removes. Green needs both halves. --- crates/biorouter/tests/privacy_capability.rs | 93 ++++++++++---------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/crates/biorouter/tests/privacy_capability.rs b/crates/biorouter/tests/privacy_capability.rs index 9bb20f867..ef5db6b08 100644 --- a/crates/biorouter/tests/privacy_capability.rs +++ b/crates/biorouter/tests/privacy_capability.rs @@ -140,30 +140,39 @@ const EXPECTED: &[Site] = &[ what: "TWO inspectors in this file, each deciding for a batch it sees \ BEFORE the dispatch that would admit a capability — that is the \ point of an inspector — so neither has one in scope to inherit on \ - the ordinary agent path, and the alternative to sampling is not \ + the ordinary agent loop, and the alternative to sampling is not \ inheriting but deciding on `Config::global()`, which is the bug \ this type exists to prevent. \ - (1) `WorkspaceCrossingInspector::inspect_with_pinned_capability`, \ - the first-crossing disclosure, sampled ONCE per batch rather than \ - per request, because two calls in one batch must not be able to \ - gate on two different models, and only after a cheap name check \ - has established that the batch contains a cross-session write at \ - all: an ordinary turn must not pay a provider-mutex read for a \ - disclosure that cannot apply to it. \ - (2) `WorkspaceMutationInspector::inspect_with_pinned_capability`'s \ - `workspace_set_tools` pre-flight (QA finding F4), which asks \ - `WorkspaceClient::set_tools_preflight_refusal` whether the \ - tool-set change CAN be made before the user is asked to approve \ - it — a card for an impossible change is a request for authority \ - over nothing — and therefore needs the caller's pair to ask the \ - tier gate with. It samples LAZILY, only once a `set_tools` call \ - has been found in the batch, and memoises the result in `sampled`, \ - so an ordinary batch pays no provider-mutex read and two such \ - calls in one batch still gate on one model. \ - ⚠ Both PREFER a capability threaded in and sample only when handed \ - `None`: a bridge grant supplies the pair it fixed at issue time, \ - which is what keeps a bridged coding agent's calls from re-reading \ - the flag on this side of the process boundary", + (1) `WorkspaceCrossingInspector`, the first-crossing disclosure, \ + sampled ONCE per batch rather than per request, because two calls \ + in one batch must not be able to gate on two different models, and \ + only after a cheap name check has established that the batch \ + contains a cross-session write at all: an ordinary turn must not \ + pay a provider-mutex read for a disclosure that cannot apply to \ + it. \ + (2) `WorkspaceMutationInspector`'s `workspace_set_tools` \ + pre-flight (QA finding F4). The pair it samples is handed to \ + `set_tools_preflight_refusal` → `preflight_set_tools`, which asks \ + §7's write row (`refuse_unless_writable`), Gate F reach for each \ + ADDED extension, the manageability refusal for each REMOVED one, \ + and `privacy::bind_allowed` for a provider switch — so the answer \ + is either a Deny carrying the handler's own sentence or the \ + always-confirm card, and a card for a change this caller's model \ + may not make asks the user to authorise nothing. It is NOT the \ + gate: `handle_set_tools` re-runs the same pre-flight against the \ + capability the call is finally admitted on, so a model swapped \ + between inspection and dispatch can only make this sample stale in \ + the fail-safe direction. \ + ⚠ Both funnel into `inspect_with_pinned_capability`, whose first \ + act is `let mut sampled = capability` — a pinned pair is used AS \ + IS and the provider mutex is never read. The only caller that pins \ + one is the coding-agent bridge, which threads the pair it fixed at \ + issue time so a bridged child's calls cannot re-read the flag \ + across the process boundary; the agent loop and the approval relay \ + pass `None`. (2) additionally samples LAZILY — inside the \ + `is_set_tools_call` arm only, memoising back into `sampled` — so a \ + batch carrying no `workspace_set_tools` call samples nothing at \ + all, and one carrying two still gates on one model", }, Site { needle: "CallCapability::sample(", @@ -296,31 +305,21 @@ const EXPECTED: &[Site] = &[ Site { needle: "CallCapability::public_enforced(", file: "crates/biorouter/src/providers/coding_agent/bridge.rs", - count: 2, - what: "NOT a production decider: the bridge's two TEST fixtures, each \ - taking the most restrictive pair rather than a permissive one \ - invented for a test's convenience. (1) `mod tests`' \ - `test_capability()`, which every `BridgeGrant` the bridge's own \ - unit tests build takes its pair from. (2) the module-level \ - `inert_grant_for_test()` — `#[cfg(test)] pub(crate)`, a grant that \ - dispatches nothing and needs no runtime — which tests in OTHER \ - modules of this crate build on. They are two rather than one \ - because a `pub(crate)` fixture cannot live inside `mod tests`, and \ - from outside that module its private `test_capability()` is not \ - visible; funnelling both through one helper at module level would \ - return this row to 1 and is the bridge authors' call, not this \ - census's. Counted for the same reason `privacy/grant.rs`'s test \ - helper is — a line-wise grep cannot tell a `#[cfg(test)]` block \ - from production, and a filter that tried would blind the census to \ - production too. The production side of this bridge decides in \ - `Agent::issue_tool_bridge`, which is a `sample(` row above. \ - ⚠ This row read `dummy_grant()` while four inline spellings had \ - accumulated in that file, so the census sat RED through a whole \ - branch whose own gate list never ran this binary; it then sat RED \ - again from `inert_grant_for_test()`'s arrival until CI began \ - running `tests/*.rs` and anyone saw it. A new test that inlines the \ - constructor rather than calling one of these two still moves the \ - count and still fires here", + count: 1, + what: "NOT a production decider: `mod tests`' `test_capability()` helper, \ + which every `BridgeGrant` the bridge's own unit tests build takes its \ + pair from — the most restrictive one, rather than a permissive one \ + invented for a test's convenience. Counted for the same reason \ + `privacy/grant.rs`'s test helper is — a line-wise grep cannot tell a \ + `#[cfg(test)]` block from production, and a filter that tried would \ + blind the census to production too. The production side of this \ + bridge decides in `Agent::issue_tool_bridge`, which is a `sample(` \ + row above. ⚠ This row read `dummy_grant()` while four inline \ + spellings had accumulated in that file, so the census sat RED \ + through a whole branch whose own gate list never ran this binary. \ + The single helper is what stops it drifting that way again: a new \ + test that inlines the constructor still moves the count off 1 and \ + still fires here", }, // ---------------------------------------------------------- DR-26's third // axis, asked through the free function rather than off a capability. Its From 77394ab841133668c281fb658f6986a65638bf92 Mon Sep 17 00:00:00 2001 From: Wanjun Gu Date: Fri, 11 Sep 2026 15:31:18 -0700 Subject: [PATCH 3/3] test(privacy): the row names each inspector's own mechanism, and the third outcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of #258 caught two prose inaccuracies, both checked against the code: the ⚠ sentence attributed `let mut sampled = capability` to BOTH inspectors, but the crossing inspector has no such binding — it matches the `Option` once at its sample point, after its early return; and the pre-flight has a third outcome besides Deny and the card, namely no card-worthy reason and on to dispatch. The property the census protects is unchanged; only the mechanism each row names is now greppable. --- crates/biorouter/tests/privacy_capability.rs | 31 ++++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/biorouter/tests/privacy_capability.rs b/crates/biorouter/tests/privacy_capability.rs index ef5db6b08..ddb1e7d00 100644 --- a/crates/biorouter/tests/privacy_capability.rs +++ b/crates/biorouter/tests/privacy_capability.rs @@ -156,23 +156,28 @@ const EXPECTED: &[Site] = &[ §7's write row (`refuse_unless_writable`), Gate F reach for each \ ADDED extension, the manageability refusal for each REMOVED one, \ and `privacy::bind_allowed` for a provider switch — so the answer \ - is either a Deny carrying the handler's own sentence or the \ - always-confirm card, and a card for a change this caller's model \ - may not make asks the user to authorise nothing. It is NOT the \ + is a Deny carrying the handler's own sentence, the always-confirm \ + card when the change is one §5 asks about, or silence and on to \ + dispatch; a card for a change this caller's model may not make \ + asks the user to authorise nothing. It is NOT the \ gate: `handle_set_tools` re-runs the same pre-flight against the \ capability the call is finally admitted on, so a model swapped \ between inspection and dispatch can only make this sample stale in \ the fail-safe direction. \ - ⚠ Both funnel into `inspect_with_pinned_capability`, whose first \ - act is `let mut sampled = capability` — a pinned pair is used AS \ - IS and the provider mutex is never read. The only caller that pins \ - one is the coding-agent bridge, which threads the pair it fixed at \ - issue time so a bridged child's calls cannot re-read the flag \ - across the process boundary; the agent loop and the approval relay \ - pass `None`. (2) additionally samples LAZILY — inside the \ - `is_set_tools_call` arm only, memoising back into `sampled` — so a \ - batch carrying no `workspace_set_tools` call samples nothing at \ - all, and one carrying two still gates on one model", + ⚠ Each funnels into its OWN `inspect_with_pinned_capability`, \ + and in both a pinned pair WINS and the provider mutex is never \ + read — but by different mechanisms, so grep for the right one: (2) \ + binds `let mut sampled = capability` up front and memoises into \ + it, while (1) has no `sampled` binding at all and matches the \ + `Option` once at its sample point, after the early return above. \ + The only caller that pins a pair is the coding-agent bridge, which \ + threads the one it fixed at issue time so a bridged child's calls \ + cannot re-read the flag across the process boundary; the agent \ + loop, the approval relay and the non-capability entry point pass \ + `None`. (2) additionally samples LAZILY — inside the \ + `is_set_tools_call` arm only — so a batch carrying no \ + `workspace_set_tools` call samples nothing at all, and one \ + carrying two still gates on one model", }, Site { needle: "CallCapability::sample(",