A refused package is invisible on the MCP surface: archive_refused reaches no coverage_reasons code, so an agent's answer is qualified by nothing #124

Closed
opened 2026-09-04 18:33:55 +02:00 by buildagent · 3 comments
Member

Found while retiring the declarative tier (#76). Pre-existing, not introduced by that change — but the retirement makes it reachable on upgrade for the first time, which is why it is worth filing now.

The gap

eligibility::requested_not_installed() filters for package_requested_not_installed, and signature_refusals() for the signature family. Neither covers everything on PackageSet::refused().

So an archive_refused from Store::get — or any other refusal on that list — produces no coverage_reasons code at all. plugin doctor, plugin status and the daemon log all show it. An agent reading project_overview or index_coverage gets an answer qualified by nothing.

It is true of digest_mismatch today too, so the class is wider than the one trigger that surfaced it.

Why the retirement makes it matter now

Every release through v0.26.1 admitted a declarative package. On upgrade, such a package is now refused at Store::get with archive_refused — correctly, and with the tier's own witness in the CLI. But a project that had one enabled will, through MCP, simply stop having those files' symbols, with nothing in any tool reply explaining why.

That is the exact shape this project keeps closing elsewhere: a fact the system knows, disclosed on one surface and silent on its neighbours. #101 and #99 closed it for truncated extractions and structural zeros; #114 closed a README claim that outran its analysis. This is the package-refusal instance, and it is arguably worse, because the files simply disappear from answers rather than returning a qualified zero.

Shape of the fix

Cover the whole of PackageSet::refused(), not one more member of it. Two filters exist because two reasons were handled; a third filter would be the third instance of the same omission. The honest form is: every refusal on that list maps to a coverage code, and a refusal with no mapping fails the build — the inversion reason_code_registry.rs already performs for the reason-code vocabulary.

Three-state discipline applies as usual: absent means this daemon did not report, [] is a measurement that nothing was refused, non-empty is the finding.

Worth noting [] only recently became reachable at all — the declarative retirement removed the constant that made coverage_reasons non-empty on every project, so the "empty is a measurement" sentence describes a real state for the first time since it was written.

Test

A project with an installed package that Store::get refuses, driven through the MCP surface, asserting the reply names the refusal. Mutation: drop the new mapping and the reply must go back to being silent — red on a payload where files really did lose their symbols, not on a hand-built struct.

#101, #99 (the same asymmetry on other facts), #107 (resources bypassing the grader), #115 (a package refusal silently promoted — the write side of the same blind spot). Found by the #76 declarative-tier retirement.

Found while retiring the declarative tier (#76). **Pre-existing**, not introduced by that change — but the retirement makes it reachable on upgrade for the first time, which is why it is worth filing now. ## The gap `eligibility::requested_not_installed()` filters for `package_requested_not_installed`, and `signature_refusals()` for the signature family. Neither covers everything on `PackageSet::refused()`. So an **`archive_refused`** from `Store::get` — or any other refusal on that list — produces **no `coverage_reasons` code at all**. `plugin doctor`, `plugin status` and the daemon log all show it. An agent reading `project_overview` or `index_coverage` gets an answer **qualified by nothing**. It is true of `digest_mismatch` today too, so the class is wider than the one trigger that surfaced it. ## Why the retirement makes it matter now Every release through v0.26.1 admitted a declarative package. On upgrade, such a package is now refused at `Store::get` with `archive_refused` — correctly, and with the tier's own witness in the CLI. But a project that had one enabled will, through MCP, simply stop having those files' symbols, with **nothing in any tool reply explaining why**. That is the exact shape this project keeps closing elsewhere: a fact the system knows, disclosed on one surface and silent on its neighbours. #101 and #99 closed it for truncated extractions and structural zeros; #114 closed a README claim that outran its analysis. This is the package-refusal instance, and it is arguably worse, because the files simply disappear from answers rather than returning a qualified zero. ## Shape of the fix Cover the **whole** of `PackageSet::refused()`, not one more member of it. Two filters exist because two reasons were handled; a third filter would be the third instance of the same omission. The honest form is: every refusal on that list maps to a coverage code, and a refusal with no mapping fails the build — the inversion `reason_code_registry.rs` already performs for the reason-code vocabulary. Three-state discipline applies as usual: absent means this daemon did not report, `[]` is a measurement that nothing was refused, non-empty is the finding. Worth noting `[]` only recently became reachable at all — the declarative retirement removed the constant that made `coverage_reasons` non-empty on every project, so the "empty is a measurement" sentence describes a real state for the first time since it was written. ## Test A project with an installed package that `Store::get` refuses, driven through the **MCP** surface, asserting the reply names the refusal. Mutation: drop the new mapping and the reply must go back to being silent — red on a payload where files really did lose their symbols, not on a hand-built struct. ## Related #101, #99 (the same asymmetry on other facts), #107 (resources bypassing the grader), #115 (a package refusal silently promoted — the write side of the same blind spot). Found by the #76 declarative-tier retirement.
Author
Member

Triage 2026-09-06: LEFT OPEN — the headline defect is fixed, but this issue names the wrong list, and the list it names is still uncovered.

Reported as fixed, and the fix is good. I am leaving it open because of a distinction the fixing commit itself makes.

What is fixed — completely

archive_refused and digest_mismatch ride PackageSet::unapproved(), and that channel is now covered in full, with no filter:

  • crates/indexer/src/coverage.rs:307 — pub const PACKAGE_REFUSAL_COVERAGE_REASONS: &[&str] = crate::approval::APPROVAL_REASONS; — the constant itself, and the doc at :275 says "not a copy of it". A 22nd approval reason becomes a coverage code the day it is declared. There is no third filter and no registry of exceptions to keep true, which is exactly what this issue demanded.
  • SignatureRefusals (u8, 4 codes) became PackageRefusals (u32, the family) — set type at :335, observe at :348.
  • MCP side: crates/mcp-server/src/eligibility.rs:169-172 — package_refusals: Option<PackageRefusals>, None = did not report, Some(empty) = measured none, with explicit skew handling in both directions at :713-725.
  • The inversion gate exists: coverage.rs:1399 — "the family outgrew PackageRefusals' u32; widen the field before appending".

And it is graded through MCP against a real store, as asked:

$ cargo test -p code-index-mcp --test coverage_signature_e2e -- \
    a_refused_archive_is_reported_and_is_not_called_permanent
WARN an approved package's bytes are not usable; it is not active
     digest=sha256:dda88c1e… reason="archive_refused"
     witness=/tmp/.tmp0buCSk/operator-home/packages/sha256_dda88c1e….cip: package.malformed
test a_refused_archive_is_reported_and_is_not_called_permanent ... ok
EXIT=0

That log line is from Store::get — a real refusal, not a hand-built struct. The test title also carries the right second property: not called permanent.

Why it stays open

73ef473 states it plainly, and it is a correction to this issue rather than to the fix:

#124 NAMES THE WRONG LIST and the fix is scoped honestly. Its examples ride unapproved(), which this closes TOTALLY; refused() is separately populated from load_one with a different type and still reaches no coverage code. Recorded in-tree on the installed_but_abi_rejected row rather than papered over.

So: this issue's fix shape says "cover the whole of PackageSet::refused(), not one more member of it", and PackageSet::refused() — crates/indexer/src/packages.rs:778, returning &[RefusedPackage] — is a different list from the one that was covered. It is still silent on the MCP surface.

The examples in the body are fixed. The rule in the body is not. Closing on the examples would retire the rule at the moment it is half-applied — and the rule is the part this issue was actually written to establish, since a third filter for a third list is precisely the failure it names.

What closing needs

Either:

  1. Bring refused() / load_one's RefusedPackage under a coverage mapping the same way unapproved() now is — one relation, no third filter; or
  2. Establish in-source that refused() cannot reach an agent-visible answer (if that is true), and record it on the installed_but_abi_rejected row where the note already lives, at crates/indexer/src/coverage.rs:612.

Option 2 is legitimate and may well be the right answer — but it has not been argued, and "still reaches no coverage code" is not the same as "cannot".

🤖 Triage lane, 2026-09-06, master 45cf6e4

## Triage 2026-09-06: LEFT OPEN — the headline defect is fixed, but **this issue names the wrong list**, and the list it names is still uncovered. Reported as fixed, and the fix is good. I am leaving it open because of a distinction the fixing commit itself makes. ### What is fixed — completely `archive_refused` and `digest_mismatch` ride `PackageSet::unapproved()`, and that channel is now covered **in full, with no filter**: - `crates/indexer/src/coverage.rs:307` — `pub const PACKAGE_REFUSAL_COVERAGE_REASONS: &[&str] = crate::approval::APPROVAL_REASONS;` — the constant itself, and the doc at `:275` says *"not a copy of it"*. A 22nd approval reason becomes a coverage code the day it is declared. There is no third filter and no registry of exceptions to keep true, which is exactly what this issue demanded. - `SignatureRefusals` (u8, 4 codes) became `PackageRefusals` (u32, the family) — set type at `:335`, `observe` at `:348`. - MCP side: `crates/mcp-server/src/eligibility.rs:169-172` — `package_refusals: Option<PackageRefusals>`, `None` = did not report, `Some(empty)` = measured none, with explicit skew handling **in both directions** at `:713-725`. - The inversion gate exists: `coverage.rs:1399` — *"the family outgrew PackageRefusals' u32; widen the field before appending"*. And it is graded through MCP against a real store, as asked: ``` $ cargo test -p code-index-mcp --test coverage_signature_e2e -- \ a_refused_archive_is_reported_and_is_not_called_permanent WARN an approved package's bytes are not usable; it is not active digest=sha256:dda88c1e… reason="archive_refused" witness=/tmp/.tmp0buCSk/operator-home/packages/sha256_dda88c1e….cip: package.malformed test a_refused_archive_is_reported_and_is_not_called_permanent ... ok EXIT=0 ``` That log line is from `Store::get` — a real refusal, not a hand-built struct. The test title also carries the right second property: **not called permanent**. ### Why it stays open `73ef473` states it plainly, and it is a correction to this issue rather than to the fix: > **#124 NAMES THE WRONG LIST** and the fix is scoped honestly. Its examples ride `unapproved()`, which this closes TOTALLY; **`refused()` is separately populated from `load_one` with a different type and still reaches no coverage code.** Recorded in-tree on the `installed_but_abi_rejected` row rather than papered over. So: this issue's **fix shape** says *"cover the whole of `PackageSet::refused()`, not one more member of it"*, and `PackageSet::refused()` — `crates/indexer/src/packages.rs:778`, returning `&[RefusedPackage]` — is a **different list** from the one that was covered. It is still silent on the MCP surface. The examples in the body are fixed. The rule in the body is not. Closing on the examples would retire the rule at the moment it is half-applied — and the rule is the part this issue was actually written to establish, since a third filter for a third list is precisely the failure it names. ### What closing needs Either: 1. Bring `refused()` / `load_one`'s `RefusedPackage` under a coverage mapping the same way `unapproved()` now is — one relation, no third filter; or 2. Establish in-source that `refused()` **cannot** reach an agent-visible answer (if that is true), and record it on the `installed_but_abi_rejected` row where the note already lives, at `crates/indexer/src/coverage.rs:612`. Option 2 is legitimate and may well be the right answer — but it has not been argued, and "still reaches no coverage code" is not the same as "cannot". 🤖 Triage lane, 2026-09-06, master `45cf6e4`
Author
Member

FIXED — the second list is covered, by the same mechanism and with no third filter. Commit 378f7f6 on lane/pkg-124-refused-coverage.

The previous triage was right to leave this open and right about why: "this issue's fix shape says cover the whole of PackageSet::refused(), and that is a DIFFERENT list from the one that was covered." It offered two ways to close.

Option 2 was checked first and it is FALSE. refused() really can reach an agent-visible answer: load_one refuses a package the operator installed AND approved, whose bytes the store handed back — so the extensions it claims have no producer and its files lose their symbols, while every reply stays silent. That is not "cannot reach"; it is "did reach, unqualified". So option 1.

The mechanism, and why it is one relation rather than a second channel

PackageRefusals' vocabulary is now the union of the two closed families, one per list, both DERIVED and neither enumerated:

PACKAGE_REFUSAL_COVERAGE_REASONS = APPROVAL_REASONS ++ PACKAGE_LOAD_REFUSAL_COVERAGE_REASONS
PACKAGE_LOAD_REFUSAL_COVERAGE_REASONS = Reason::ALL projected through Reason::as_str

both concatenated in a const fn. A twenty-second approval reason and a fifty-fourth Reason are each a coverage code the day they are declared. Reason::as_str became const for exactly this — the alternative was a hand-written list, which is the defect this issue was filed about.

No new wire field: the refusals ride the same package_refusals key, because they answer the same question an agent asked. daemon::eligibility::package_refusals now sweeps both lists and filters neither.

"A refusal with no mapping fails the build" is a property of the TYPE. observe_reason takes a code_index_abi::Reason, not a string, so the mapping cannot miss — a caller cannot hand over a string that falls outside the family because it cannot hand over a string at all. A runtime check would have been the wrong shape: it fires on the refusals a fixture happens to produce, and every refusal never produced under a test stays unmapped and silent, which is this issue exactly.

Graded through MCP, against a real store

coverage_signature_e2e::a_package_the_host_refused_at_load_is_reported_and_is_not_called_permanent. Two packages installed and approved by the operator; the second's extractor.wasm is not a WebAssembly module, --precompile refuses it, load_one puts it on refused() with Reason::ExtractorMalformed. Nothing hand-built.

It is stronger than the sibling above it in one deliberate way: the refused package is the only claimant of .lockx, so data.lockx is code the project asked to have indexed and does not have — which is what lets this test reach the #140 permanence branch the sibling cannot (the sibling's path is claimed by a package that IS running). A healthy package stays active throughout, so a build that refused everything fails the anti-vacuity arm rather than passing.

The reply the mutation prints is this issue verbatim:

"coverage_reasons":["extractor.malformed"]
"verdict":"never"  "reason":"ineligible_extension"
"hint":"Permanent, at any size, for this project's plugin set. Use shell for this path."

Mutations — five, all run, all RED

# mutation result
1 delete the for r in host.set().refused() sweep — the pre-change state, restored exactly RED: an approved package this build refused reaches an agent as silence: []
2 narrow the load family to the four signature codes — "somebody enumerated the reachable subset", in its historical shape RED, and not where I first wrote down: it dies in the daemon worker on the drift guard, before the reply is built. Recorded as measured. Also RED in the unit gate, in both profiles — --release, with the debug_assert! compiled out, fails on the test's own assertion left: [] right: ["abi.version_unsupported"]
3 delete index_coverage's if fixable { … } else RED — the reply above
4 install only the healthy package (anti-vacuity) RED at the refusal assertion, healthy control still passing — which is what proves the assertion is carried by the broken package
5 drop the load family's anchor from SEMANTICS RED: the load half names no member at all

Mutation 2 in both profiles is the one worth calling out: the guard behind observe_reason is a debug_assert!, and this repository does not let a profile-dependent check carry a test.

Two existing gates caught the widening. Both were MOVED, not weakened

  • the_bitset_records_only_the_family_and_keeps_its_order used fact.span_out_of_range as its negative control — a code from a different closed vocabulary. That vocabulary is now half the family. A control naming a member of the family it controls for must move, so it is now plugin_state_unavailable, a host-minted Coverage code, with only_a_package_refusal_contradicts_permanence asserting the same separation from the other side.
  • the_semantics_names_every_code_the_field_can_carry now anchors both families to a real member instead of one.

The #140 permanence clause was re-derived rather than assumed: a Reason on this channel names a package the operator installed, so removing, replacing or rebuilding it changes the verdict — the widened family still contradicts a permanence claim for every member, and the gate asserts it over the whole widened set.

The pointer that this fix made false, fixed in the same change

EXTENSION_STATES_NOT_DERIVED's installed_but_abi_rejected row said "That list is NOT the one coverage_reasons reads." True when written, false the moment this landed. It now carries elsewhere: Some("plugin_activation.coverage_reasons") and says which half is still not derived (the per-EXTENSION row, which really would need the manifest this build just refused). #140's pointer gate is what keeps it honest. code-index://docs/reason-codes documents the two families.

One thing that broke, and the gate that caught it is the story

Making Reason::as_str const broke reason_code_registry: it locates the method by a source-text needle spelling pub fn, at three sites, so all three stopped matching at once and every parser in that file returned the empty set. the_populations_are_not_empty caught it by its floor —

Reason::as_str parsed 0 codes ({}), floor 45 — the parser is broken and every gate in this file would be vacuous

— which is an anti-vacuity check doing exactly its job, on a one-word change in another crate. The needle now starts at fn and is one constant instead of three literals.

All FOUR PackageSet lists now reach an agent

packages(), unapproved() (#124 first pass), refused() (this), duplicate_package_ids() (#91, via package_duplicate_ids). That is the closure argument this issue asked for: not "one more member", but every list.

Gates

cargo fmt --all -- --check 0 · cargo clippy --workspace --all-targets -- -D warnings 0 · RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items 0 · coverage_signature_e2e 3/3 · index_coverage_e2e 11/11 · reason_code_registry 12/12 · disclosure_derivation_registry 11/11 · symbol_blind_coverage_e2e 5/5 · coverage unit tests 24/24.

cargo test --workspace --no-fail-fast: 310 suites, 1 failure — overview_payload_budget_e2e, measured PRE-EXISTING and filed as #197. Master's exact semantics wording reads 4054 tokens against the 4050 ceiling; this change reads 4052, so it is 2 tokens better and still red. Not blessed, not papered over, and not trimmed out of somebody else's field to get a green run.

No protected record moved: baseline.json, stage-baseline.json, tier3-baseline.json, ruby-package-cost.json and tests/bench/oracle/* are untouched, and nothing under tests/packages/ changed — this is a read-side change with no coupled package landing behind it.

🤖 Packaged-language lane, 2026-09-06, master 4f866e5, worktree /tmp/cosi-lane-pkg

## FIXED — the second list is covered, by the same mechanism and with no third filter. Commit `378f7f6` on `lane/pkg-124-refused-coverage`. The previous triage was right to leave this open and right about why: *"this issue's fix shape says cover the whole of `PackageSet::refused()`, and that is a DIFFERENT list from the one that was covered."* It offered two ways to close. **Option 2 was checked first and it is FALSE.** `refused()` really can reach an agent-visible answer: `load_one` refuses a package the operator installed AND approved, whose bytes the store handed back — so the extensions it claims have no producer and its files lose their symbols, while every reply stays silent. That is not "cannot reach"; it is "did reach, unqualified". So option 1. ### The mechanism, and why it is one relation rather than a second channel `PackageRefusals`' vocabulary is now the **union of the two closed families, one per list**, both DERIVED and neither enumerated: ```rust PACKAGE_REFUSAL_COVERAGE_REASONS = APPROVAL_REASONS ++ PACKAGE_LOAD_REFUSAL_COVERAGE_REASONS PACKAGE_LOAD_REFUSAL_COVERAGE_REASONS = Reason::ALL projected through Reason::as_str ``` both concatenated in a `const fn`. A twenty-second approval reason and a fifty-fourth `Reason` are each a coverage code **the day they are declared**. `Reason::as_str` became `const` for exactly this — the alternative was a hand-written list, which is the defect this issue was filed about. No new wire field: the refusals ride the same `package_refusals` key, because they answer the same question an agent asked. `daemon::eligibility::package_refusals` now sweeps both lists and filters neither. **"A refusal with no mapping fails the build" is a property of the TYPE.** `observe_reason` takes a `code_index_abi::Reason`, not a string, so the mapping cannot miss — a caller cannot hand over a string that falls outside the family because it cannot hand over a string at all. A runtime check would have been the wrong shape: it fires on the refusals a fixture happens to produce, and every refusal never produced under a test stays unmapped and silent, which is this issue exactly. ### Graded through MCP, against a real store `coverage_signature_e2e::a_package_the_host_refused_at_load_is_reported_and_is_not_called_permanent`. Two packages installed and approved by the operator; the second's `extractor.wasm` is not a WebAssembly module, `--precompile` refuses it, `load_one` puts it on `refused()` with `Reason::ExtractorMalformed`. Nothing hand-built. It is stronger than the sibling above it in one deliberate way: the refused package is the **only** claimant of `.lockx`, so `data.lockx` is code the project asked to have indexed and does not have — which is what lets this test reach the #140 permanence branch the sibling cannot (the sibling's path is claimed by a package that IS running). A healthy package stays active throughout, so a build that refused everything fails the anti-vacuity arm rather than passing. The reply the mutation prints is this issue verbatim: ``` "coverage_reasons":["extractor.malformed"] "verdict":"never" "reason":"ineligible_extension" "hint":"Permanent, at any size, for this project's plugin set. Use shell for this path." ``` ### Mutations — five, all run, all RED | # | mutation | result | |---|---|---| | 1 | delete the `for r in host.set().refused()` sweep — the pre-change state, restored exactly | **RED**: `an approved package this build refused reaches an agent as silence: []` | | 2 | narrow the load family to the four signature codes — "somebody enumerated the reachable subset", in its historical shape | **RED**, and **not where I first wrote down**: it dies in the daemon worker on the drift guard, before the reply is built. Recorded as measured. Also RED in the unit gate, **in both profiles** — `--release`, with the `debug_assert!` compiled out, fails on the test's own assertion `left: [] right: ["abi.version_unsupported"]` | | 3 | delete `index_coverage`'s `if fixable { … } else` | **RED** — the reply above | | 4 | install only the healthy package (anti-vacuity) | **RED at the refusal assertion**, healthy control still passing — which is what proves the assertion is carried by the broken package | | 5 | drop the load family's anchor from `SEMANTICS` | **RED**: `the load half names no member at all` | Mutation 2 in both profiles is the one worth calling out: the guard behind `observe_reason` is a `debug_assert!`, and this repository does not let a profile-dependent check carry a test. ### Two existing gates caught the widening. Both were MOVED, not weakened - `the_bitset_records_only_the_family_and_keeps_its_order` used `fact.span_out_of_range` as its negative control — *a code from a different closed vocabulary*. That vocabulary is now **half the family**. A control naming a member of the family it controls for must move, so it is now `plugin_state_unavailable`, a host-minted Coverage code, with `only_a_package_refusal_contradicts_permanence` asserting the same separation from the other side. - `the_semantics_names_every_code_the_field_can_carry` now anchors **both** families to a real member instead of one. The #140 permanence clause was **re-derived rather than assumed**: a `Reason` on this channel names a package the operator installed, so removing, replacing or rebuilding it changes the verdict — the widened family still contradicts a permanence claim for every member, and the gate asserts it over the whole widened set. ### The pointer that this fix made false, fixed in the same change `EXTENSION_STATES_NOT_DERIVED`'s `installed_but_abi_rejected` row said *"That list is NOT the one `coverage_reasons` reads."* True when written, false the moment this landed. It now carries `elsewhere: Some("plugin_activation.coverage_reasons")` and says which half is still not derived (the per-EXTENSION row, which really would need the manifest this build just refused). `#140`'s pointer gate is what keeps it honest. `code-index://docs/reason-codes` documents the two families. ### One thing that broke, and the gate that caught it is the story Making `Reason::as_str` `const` broke `reason_code_registry`: it locates the method by a **source-text needle spelling `pub fn`**, at three sites, so all three stopped matching at once and every parser in that file returned the empty set. `the_populations_are_not_empty` caught it by its floor — > `Reason::as_str parsed 0 codes ({}), floor 45 — the parser is broken and every gate in this file would be vacuous` — which is an anti-vacuity check doing exactly its job, on a one-word change in another crate. The needle now starts at `fn` and is one constant instead of three literals. ### All FOUR `PackageSet` lists now reach an agent `packages()`, `unapproved()` (#124 first pass), `refused()` (this), `duplicate_package_ids()` (#91, via `package_duplicate_ids`). That is the closure argument this issue asked for: not "one more member", but every list. ### Gates `cargo fmt --all -- --check` **0** · `cargo clippy --workspace --all-targets -- -D warnings` **0** · `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items` **0** · `coverage_signature_e2e` **3/3** · `index_coverage_e2e` **11/11** · `reason_code_registry` **12/12** · `disclosure_derivation_registry` **11/11** · `symbol_blind_coverage_e2e` **5/5** · `coverage` unit tests **24/24**. `cargo test --workspace --no-fail-fast`: **310 suites, 1 failure** — `overview_payload_budget_e2e`, **measured PRE-EXISTING and filed as #197**. Master's exact semantics wording reads 4054 tokens against the 4050 ceiling; this change reads 4052, so it is 2 tokens *better* and still red. Not blessed, not papered over, and not trimmed out of somebody else's field to get a green run. No protected record moved: `baseline.json`, `stage-baseline.json`, `tier3-baseline.json`, `ruby-package-cost.json` and `tests/bench/oracle/*` are untouched, and nothing under `tests/packages/` changed — this is a read-side change with no coupled package landing behind it. 🤖 Packaged-language lane, 2026-09-06, master `4f866e5`, worktree `/tmp/cosi-lane-pkg`
Author
Member

FIXED and merged as e5775ae (lane commit 378f7f6).

The fix takes the shape this issue asked for, not a third filter. PackageRefusals' vocabulary is now the derived union of both closed families (APPROVAL_REASONS ++ Reason::ALL in a const fn), it sweeps both lists, and "an unmapped refusal fails the build" became a property of the type: observe_reason takes a Reason, not a string. All four PackageSet lists now reach MCP.

The cheaper hypothesis was checked first and refuted: refused() really does reach an agent, so the disclosure was genuinely absent rather than already covered by a neighbouring path.

Five mutations run, all RED — including one that had to be run in both profiles, because the guard behind it is a debug_assert! and would have been vacuous in release. Two existing gates caught the widening and were moved rather than weakened.

Closing.

FIXED and merged as `e5775ae` (lane commit `378f7f6`). The fix takes the shape this issue asked for, not a third filter. `PackageRefusals`' vocabulary is now the **derived** union of both closed families (`APPROVAL_REASONS ++ Reason::ALL` in a `const fn`), it sweeps both lists, and "an unmapped refusal fails the build" became a property of the **type**: `observe_reason` takes a `Reason`, not a string. All four `PackageSet` lists now reach MCP. The cheaper hypothesis was checked first and refuted: `refused()` really does reach an agent, so the disclosure was genuinely absent rather than already covered by a neighbouring path. Five mutations run, all RED — including one that had to be run in **both** profiles, because the guard behind it is a `debug_assert!` and would have been vacuous in release. Two existing gates caught the widening and were **moved rather than weakened**. Closing.
Sign in to join this conversation.
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
h-dv/code-index#124
No description provided.