[v0.1.x/Proto] Bind the gated-field list to the Requires call sites it claims #175

Closed
opened 2026-09-07 14:16:45 +09:00 by natsukium · 0 comments
Owner

What was found

crates/felis-protocol/src/minor.rs records every gated field twice: once in the hand-written
GATED_FIELDS list, and once at the Requires::when(...) call site inside the owning MinorGated::requires
impl. Nothing binds the two together.

The ledger-coverage test (the_ledger_covers_every_executable_addition) walks GATED_FIELDS, so deleting a
Requires::when call from a requires() arm while leaving its GATED_FIELDS entry in place still passes:
the send gate silently stops authorizing that field while the prose ledger and the test both keep claiming it
does. The reverse — an entry added to GATED_FIELDS with no call site — is equally invisible.

Why it is out of scope for #50

#50's acceptance criterion is that the prose ledger and the executable metadata cover the same additions;
both directions of that check now run. Closing this gap instead means changing how a field declares its
requirement — colocating the Requires constant with the field it gates and enumerating them through a macro
or trait, so the list and the call sites cannot be written separately. That is a design of its own, and #50 is
already at its size budget.

What the follow-up should do

Derive the gated-field set structurally from the call sites requires() reads, so a field's authorization has
exactly one home, then point the ledger-coverage test at that derived set. Per-field coverage of the current
call sites (a message built with each field present, asserting requires().minor) is the cheaper fallback if
the structural version proves too invasive before the freeze.

Split from #50.

## What was found `crates/felis-protocol/src/minor.rs` records every gated field twice: once in the hand-written `GATED_FIELDS` list, and once at the `Requires::when(...)` call site inside the owning `MinorGated::requires` impl. Nothing binds the two together. The ledger-coverage test (`the_ledger_covers_every_executable_addition`) walks `GATED_FIELDS`, so deleting a `Requires::when` call from a `requires()` arm while leaving its `GATED_FIELDS` entry in place still passes: the send gate silently stops authorizing that field while the prose ledger and the test both keep claiming it does. The reverse — an entry added to `GATED_FIELDS` with no call site — is equally invisible. ## Why it is out of scope for #50 #50's acceptance criterion is that the prose ledger and the executable metadata cover the same additions; both directions of that check now run. Closing this gap instead means changing how a field declares its requirement — colocating the `Requires` constant with the field it gates and enumerating them through a macro or trait, so the list and the call sites cannot be written separately. That is a design of its own, and #50 is already at its size budget. ## What the follow-up should do Derive the gated-field set structurally from the call sites `requires()` reads, so a field's authorization has exactly one home, then point the ledger-coverage test at that derived set. Per-field coverage of the current call sites (a message built with each field present, asserting `requires().minor`) is the cheaper fallback if the structural version proves too invasive before the freeze. Split from #50.
Sign in to join this conversation.
No description provided.