Skip to content

serviceability: a permissionless flag on Feed, declared not enforced - #4246

Open
Jared-dz wants to merge 1 commit into
mainfrom
feat/feed-permissionless-flag
Open

serviceability: a permissionless flag on Feed, declared not enforced#4246
Jared-dz wants to merge 1 commit into
mainfrom
feat/feed-permissionless-flag

Conversation

@Jared-dz

Copy link
Copy Markdown

RFC-1 (malbeclabs/infra#2390) needs the storefront to know whether a feed is offered without an access grant. Today that claim lives in a hand-maintained file in the website repo, so a feed and the claim about it are edited in two places and drift silently. This puts the claim on the feed. It is PR 16 of RFC-1.

The flag is declarative, and that is the design

No instruction reads it. No gate consults it. The paid gate for a feed is still the EdgeSeat FeedSeat.

That is not a shortcut. This program has no purchase path to gate. The only check a serviceability flag could have short-circuited is check_feed_metro_coverage, which is the check that a connection holds a paid seat — turning that off would make a feed free, not self-serve. Gating a purchase belongs where the purchase is, on the shreds side; malbeclabs/infra#1704 designed exactly that and was closed not-planned. If it is ever wanted, that is the account and that is the design. A reader who assumes this flag protects revenue is wrong, and the doc comment says so.

No migration, no backfill — verified, not assumed

Accounts. Feed::try_from is hand-written and deserializes field by field with unwrap_or_default(), so the 151 accounts already on mainnet read false at EOF. Pinned by a LegacyFeed encode/decode test that also asserts the fields before the new one still land correctly — a decoder that mis-read the missing byte would corrupt those, not merely the flag.

Instructions. borsh-incremental substitutes the default only when a field consumes zero bytes, and a trailing bool either does that entirely or not at all, so it can never reach the partial-read error branch. Pinned by a test that serializes CreateFeed, pops the trailing byte to get exactly what an old client emits, submits the raw bytes, and asserts the feed is created reading false.

Deploy ordering (RFC-1). The program must reach every cluster before any client that emits the flag. The failure in the wrong order is quiet rather than loud: an old program ignores trailing bytes, so a new CLI against an old program would create a feed with the flag silently dropped. Stated in the CHANGELOG, along with the note that an existing feed grows one byte on its first UpdateFeed — including a --name-only update — with the rent delta charged to the signer.

Option<bool> on update is load-bearing

process_update_feed rejects an args value equal to FeedUpdateArgs::default() as a no-op. With a bare bool, an update carrying only permissionless: false would be that default and be refused — the flag could never be turned off. Option<bool> keeps "set it false" distinct from "leave it alone", the same reason TenantUpdateArgs uses it. A test flips it on at create, off, and on again.

The CLI mirrors the split: feed create --permissionless is a presence flag, feed update --permissionless true|false takes a value.

All four decoders, because there is no IDL

Rust, Go, Python and TypeScript move together. Go has no ReadBool, so it uses the (reader.ReadU8() != 0) idiom the Tenant decoder already uses. Each is EOF-safe already, and each gains a truncation test asserting a pre-flag account reads false.

feed.bin goes 154 → 155 bytes and the fixture sets permissionless: true, so a decoder that skips the byte is caught rather than passing by coincidence. make check-fixtures is clean.

feed list gains a permissionless column, in the table and both JSON forms; the golden table string is rebuilt by hand.

Also corrected

The four Feed doc comments called one account a SKU. Under malbeclabs/infra#2390 a SKU is every Feed sharing a code — one account is a feed, and its pubkey is the feed_key. Same correction already made in doublezerofoundation/doublezero.xyz-new#1015. These structs were open anyway.

Checks

  • 322 program lib tests, 9 feed integration tests (2 new), 437 CLI, 200 SDK — all pass.
  • Go, Python and TypeScript fixture suites pass, including the three new truncation tests.
  • cargo fmt --all --check, gofmt -l, and cargo clippy --all-targets clean on every touched crate.
  • make check-fixtures clean.

Not carried: make generate-fixtures also refreshes three generator Cargo.lock files that the v0.38.0 release left on 0.37.0. That churn is unrelated to this change and the Makefile excludes lockfiles from the drift check, so it is reverted here rather than folded in.

RFC-1 (malbeclabs/infra#2390) needs the storefront to know whether a feed is
offered without an access grant. Today that lives in a hand-maintained file
in the website repo, so a feed and the claim about it are edited in two
places and drift silently. This puts the claim on the feed.

The flag is DECLARATIVE. No instruction reads it, no gate consults it, and
the paid gate is still the EdgeSeat FeedSeat. That is not a shortcut: this
program has no purchase path to gate. The only check a serviceability flag
could have short-circuited is check_feed_metro_coverage, which is the check
that a connection holds a paid seat -- turning that off would make a feed
free, not self-serve. Gating a purchase belongs on the shreds side, where
the purchase is (malbeclabs/infra#1704, closed not-planned).

No migration, no backfill, verified rather than assumed:

  - Accounts. Feed::try_from is hand-written and deserializes field by field
    with unwrap_or_default(), so the 151 accounts already on mainnet read
    false at EOF. Pinned by a LegacyFeed encode/decode test that also asserts
    the fields BEFORE the new one still land correctly -- a decoder that
    mis-read the missing byte would corrupt those, not just the flag.

  - Instructions. borsh-incremental substitutes the default only when a field
    consumes zero bytes, which a trailing bool either does entirely or not at
    all, so it can never hit the partial-read error branch. Pinned by a test
    that serializes CreateFeed, pops the trailing byte to get exactly what an
    old client emits, submits the raw bytes, and asserts the feed is created
    reading false.

FeedUpdateArgs.permissionless is Option<bool>, not bool, because the
processor rejects an args value equal to the default as a no-op: a bare
false would be indistinguishable from one, leaving no way to turn the flag
back off. A test flips it on at create, off, and on again.

The CLI mirrors that split -- `feed create --permissionless` is a presence
flag, `feed update --permissionless true|false` takes a value -- and
`feed list` gains a permissionless column in the table and both JSON forms.
The golden table string is rebuilt by hand.

All four decoders move together, since there is no IDL: Rust, Go
(ReadU8() != 0, the Tenant idiom -- there is no ReadBool), Python and
TypeScript. Each is EOF-safe already, and each gains a truncation test
asserting a pre-flag account reads false. feed.bin goes 154 -> 155 bytes
with permissionless: true, so a decoder that skips the byte is caught rather
than passing by coincidence.

Also corrected while these structs were open: the four Feed doc comments
called one account a SKU. A SKU is every Feed sharing a code
(malbeclabs/infra#2390); one account is a feed, and its pubkey is the
feed_key. Same correction as doublezerofoundation/doublezero.xyz-new#1015.

Tests: 322 program lib, 9 feed integration (2 new), 437 CLI, 200 SDK, plus
Go, Python and TypeScript fixture suites. cargo fmt, gofmt and clippy clean
on every touched crate.
@Jared-dz
Jared-dz requested a review from a team August 29, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant