control-plane-api: publications check spec-to-spec authorization via Snapshot - #3393
Open
bbartman wants to merge 5 commits into
Open
control-plane-api: publications check spec-to-spec authorization via Snapshot#3393bbartman wants to merge 5 commits into
bbartman wants to merge 5 commits into
Conversation
Contributor
Author
Approved plan (tracking)Migration of the spec-to-spec authorization leg (issue #2781), stacked on #3387. Decisions approved 2026-08-25: land as a stacked PR (not appended to #3387), and spec-to-spec denials cancel the revoke token per the every-Snapshot-denial-cancels rule.
Scope limits: no staleness contract ( |
Publications had no coverage of the writes_to leg of spec-to-spec authorization, and no pin of how grant chains behave. Add a red baseline: a dogs/ capture into cats/ is denied without a direct write grant, a chain through an intermediary role (dogs/ admin of middle/, middle/ write to cats/) does NOT authorize it under the SQL direct-row match — even though the runtime's authorize_task walks exactly this chain — and the denial leaves the Snapshot's revoke token alone. The in-process migration flips the latter two pins deliberately.
Snapshot::is_role_authorized wraps tables::RoleGrant::is_authorized the way is_user_authorized wraps the user walk, keeping grant-graph traversal hidden behind Snapshot methods. Snapshot::spec_capabilities reproduces the SQL spec_capabilities column — the role grants a spec holds by virtue of its own name — for rendering authorization errors. Both are unused until publications migrate onto them.
…Snapshot resolve_live_specs now evaluates reads_from/writes_to of drafted specs with Snapshot::is_role_authorized instead of the SQL-computed spec_capabilities column, and renders denial errors from Snapshot::spec_capabilities (identical output). Two deliberate semantic changes: - The check walks the full grant graph — the same walk which authorizes running tasks (authorize_task) — where the SQL column matched direct role_grants rows only. A spec authorized through a grant chain now verifies at publication just as it would run, and bundle-carrying grants count the same way the runtime counts them. Strictly more permissive, mirroring the accepted #2848 upward-walk divergence. - Denials request an early Snapshot refresh, consistent with every other Snapshot-evaluated denial that fails a publication: the needed role grant may postdate the Snapshot. Because the check is always evaluated — system publications included — publications through TestControlPlane::publish now depend on Snapshot freshness where SQL read role_grants live. The harness's control plane therefore moves off its DB-backed watch (whose init-time Snapshot predates test fixtures, and whose source cool-off prevents a re-take within a test) and onto the shared manually-driven watch, which publish re-takes and pins exactly as the executor poll paths do. Tests pinning snapshots before creating role grants re-pin. The pinned single-hop and no-refresh tests flip accordingly; the chained-grant publication now succeeds.
With spec-to-spec authorization migrated onto the Snapshot, nothing reads the spec_capabilities column. Drop it, its fetch flag, and the role_grants subquery from fetch_live_specs, along with the placeholder column in fetch_expanded_live_specs and the LiveSpec field. This completes the fetch/decide split for live-spec resolution: fetches are pure data reads, and every authorization decision happens in-process against the pinned Snapshot.
bbartman
force-pushed
the
bmb/2781-publications-spec-authz
branch
from
August 27, 2026 10:54
7208109 to
6b38411
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continues the #2781 strangulation: with user-capability and data-plane authorization already Snapshot-evaluated (#3387), this migrates the last SQL-computed authorization in the publications path — the spec-to-spec
reads_from/writes_tochecks — onto the pinned Snapshot, and makesfetch_live_specsa fully pure data fetch.Stacked on #3387 (
bmb/2781-publications-snapshot-authz).What changes
Snapshotgainsis_role_authorized(wrappingtables::RoleGrant::is_authorized, mirroringis_user_authorized) andspec_capabilities(the directly-held grants of a spec's name, for error rendering).resolve_live_specsevaluates spec-to-spec authorization of drafted specs against the Snapshot instead of thespec_capabilitiescolumn thatfetch_live_specscomputed from arole_grantssubquery. Error messages are rendered identically.fetch_live_specsdrops thefetch_spec_capabilitiesflag, therole_grantssubquery, and theLiveSpec.spec_capabilitiesfield (plus the placeholder column infetch_expanded_live_specs), completing the fetch/decide split: fetches are pure data reads; every authorization decision happens in-process against the pinned Snapshot.Deliberate semantic changes
role_grantsrows only.Snapshot::is_role_authorizedwalks the full grant graph — the same walk which authorizes running tasks (authorize_task). A spec authorized through a chain (e.g.dogs/admin ofmiddle/,middle/write tocats/) now verifies at publication exactly as it would run, and bundle-carrying grants count the way the runtime counts them. Strictly more permissive; mirrors the accepted Storage mapping wizard private plane grants #2848 upward-walk divergence for user capability, and closes a gap where publication could reject a draft whose running task the runtime would happily authorize.Because of (3), the test harness's control plane moves off its DB-backed watch — whose init-time Snapshot predates test fixtures, and whose source cool-off prevents a re-take within a test — and onto the shared manually-driven watch, which
TestControlPlane::publishre-takes and pins exactly as the executor poll paths do.Test plan
writes_todenial leg (with grant rendering and revoke-cancellation), and the grant-chain publication — pinned as denied under single-hop SQL in the first commit, flipped to authorized by the migration.Snapshot::spec_capabilitiesandSnapshot::is_role_authorized(legacy capability comparisons through bundle bits).test_publication_system_user_data_plane_filterobserves cancellation on the Snapshotpublishpins;test_injected_ops_collections_are_not_lockedre-pins after creating the role grant its build needs.agent(107) andcontrol-plane-api(165) suites pass.Remaining
user_roles/RLS estate after this PR (unchanged): storage-mappings directive,create_data_plane+update_l2_reportingops/checks, and the PostgREST_ext-view estate.