block / block/buzz

RFC: declarative event-kind registry to replace required_scope_for_kind (behavior-preserving; hardens 3 invariants)

Open
#3,167 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

**Motivation**

Adding an event kind today means editing several central chokepoints in lockstep: `required_scope_for_kind` (a large match in `buzz-relay`), two disjoint boolean allowlists (`is_global_only_kind` / `requires_h_channel_scope`), `is_relay_only_kind`, and the `P_GATED_KINDS` / `AUTHOR_ONLY_KINDS` / `RESULT_GATED_KINDS` slices in `buzz-core` — plus a coupled `search_tsv`-NULL rule in the migrations. Two of these invariants are held only by runtime tests: the disjointness of the two scoping allowlists, and the read-gate ↔ storage (`search_tsv`-NULL) coupling. Both are easy to break silently, in the relay's authorization path. It's also the hardest part of the tree for a new contributor to extend safely, since a correct new kind must touch all of the above consistently.

**Proposed solution**

A declarative `KindDescriptor` registry (a small new leaf crate) holding each kind's policy — `{ required_scope, scoping, read_gate, authorship, extension }` — plus a `KindExtension` trait for the residue that isn't declarative (per-kind `authorize` / `validate`). The relay resolves every incoming event's policy through the registry instead of the match + allowlists + slices.

Key properties:
- **Dogfooded, not a plugin path.** Built-in kinds register through the same path — there's no privileged "core vs plugin" branch — so this is a pure refactor of the existing surface, not a new extension mechanism bolted on.
- **Hardens three invariants structurally.** Global-vs-channel becomes one `Scoping` enum, so double-classification is *unrepresentable* (the runtime disjointness test becomes redundant). Unknown-kind stays fail-closed (a registry miss reproduces the existing `"restricted: unknown event kind"` reject). The read-gate ↔ `search_tsv`-NULL coupling gets a single source that a golden test checks against the migration's generated-column kind list, instead of two hand-maintained lists that can drift.
- **Behavior-preserving.** The existing authorization tests are the acceptance bar and pass unchanged; storage/retrieval is already kind-agnostic, so most kinds need no extra code.

**Alternatives considered**

- Keep the central match (status quo) — the invariant fragility and contributor friction remain, and every new kind keeps paying the multi-site "fork tax."
- Config/env-driven kind allowlist — can't express the per-kind `authorize`/`validate` logic some kinds need (e.g. envelope validation, identity gates).
- WASM / dynamically-loaded plugins — far heavier and unnecessary; this stays compiled-in and object-safe (the `Pin>` pattern already used by `buzz-workflow`'s `ActionSink`), adding no runtime plugin machinery.

**Additional context**

We have a complete working implementation of this against a fork, verified behavior-preserving against the existing relay test-suite (the `required_scope_for_kind` matrix, the disjointness test, the p-gate/author-only/result-gated read-path tests, the FTS drift test). We'd upstream it as a focused, review-sized PR series — built-in kinds only, with no downstream-specific kinds — once the approach is acknowledged. Happy to adjust the shape (crate placement, trait surface, phasing) to your preferences before writing the PR.

Searched open issues and PRs for duplicates (kind registry / `required_scope_for_kind` / authorization refactor): none found.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.