mirror of
https://github.com/block/buzz.git
synced 2026-11-08 10:21:46 +00:00
Remove unfenced FI policy writers and routes
Keep old receipts readable through policy status without retaining an unfenced mutation path. Require explicit CAS for every new write. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
This commit is contained in:
@@ -42,7 +42,7 @@ Acceptance serializes UUID reuse, then locks deletion-shared → community
|
||||
`FOR NO KEY UPDATE` → exclusive host lease, preserving lifecycle/deletion-intent,
|
||||
exact host, single owner and actor==owner checks and community write admission.
|
||||
Acceptance and receipt commit atomically. UUID identity binds the full tuple,
|
||||
action, both preconditions, request form and authenticated operator. Different
|
||||
action, both preconditions and authenticated operator. Different
|
||||
meaning under a committed UUID returns 409 `idempotency_conflict`. Identical
|
||||
committed replay recovers history before mutable preflight and never writes policy.
|
||||
|
||||
@@ -59,7 +59,8 @@ GET `?operation_id=<UUID>` returns only the originating operator's receipt.
|
||||
Mixed query forms are invalid. The response separates `receipt` (original tuple,
|
||||
accepted generation/action/preconditions and current target generation/flag) from
|
||||
`mode` and `effective_required`. Current-state reads also print exact expected
|
||||
precondition fields. Missing/deleted targets have null current fields.
|
||||
`expected_policy_generation` and `expected_requires_nip_fi` fields, without a
|
||||
duplicate unprefixed pair. Missing/deleted targets have null current fields.
|
||||
|
||||
| Receipt state | Meaning | CLI exit |
|
||||
|---|---|---|
|
||||
@@ -85,17 +86,27 @@ return 409 with corresponding `*_conflict` codes; generation exhaustion returns
|
||||
Migration 0084 adds desired action and nullable CAS preconditions to existing
|
||||
receipts. True/default and null preconditions preserve old receipt meaning and
|
||||
old INSERT compatibility. No data backfill, trigger, function or foreign key is
|
||||
added. Apply schema first, then compatible management writers across the **whole
|
||||
fleet** with `BUZZ_NIP_FI_CHANGES=false`, then explicitly enable mutations.
|
||||
The gate controls both directions; reads and committed replay remain available.
|
||||
Do not roll back to an unfenced management writer after the first general operation.
|
||||
added. Historical rows with null CAS preconditions remain readable through
|
||||
policy status, including after target deletion. A new fenced request cannot reuse
|
||||
such a row's UUID: null historical preconditions differ from the mandatory new
|
||||
preconditions, so it returns `idempotency_conflict` without mutation.
|
||||
|
||||
Legacy `/nip-fi-upgrade` remains enable-only. Fresh enables on optional/nonzero
|
||||
generation are refused, so an old uncommitted retry cannot reverse disable.
|
||||
Already-required legacy no-ops retain their generation. Legacy GET hides general
|
||||
CAS receipts with 404, and POST cannot reuse their UUIDs. Old binaries cannot hide
|
||||
those receipts or fence retries; the gate-off whole-fleet rollout is mandatory.
|
||||
General status can read legacy receipts, with null CAS preconditions.
|
||||
The old `/operator/communities/nip-fi-upgrade` endpoint and the `nip-fi-upgrade`
|
||||
admin command have been removed. Its GET and POST requests receive 404; use
|
||||
`nip-fi-policy` for both changes and receipt recovery. Upgrade existing clients
|
||||
for the [policy client contract](operator-nip-fi-policy-client.md) before this
|
||||
relay version reaches their fleet, with their mutations paused until rollout ends.
|
||||
No client may guess CAS fields or refresh a saved request during recovery.
|
||||
|
||||
Before rolling, close `BUZZ_NIP_FI_UPGRADES_ENABLED` on every old relay and keep
|
||||
`BUZZ_NIP_FI_CHANGES=false` on new relays. Apply schema first, roll the whole
|
||||
fleet to the supported policy writer, then explicitly enable the new gate and
|
||||
resume migrated clients. Old binaries with their old gate open can still accept
|
||||
unfenced enables and reverse a disable; a new-name setting alone cannot close
|
||||
that old gate. Retire the old setting after the last old binary is gone.
|
||||
Reads and committed replay remain available on the supported API with its gate
|
||||
closed. Do not roll back to a writer that exposes unfenced enable after any
|
||||
policy operation. This removal does not erase receipts or rewrite schema.
|
||||
|
||||
## Request enforcement and discovery
|
||||
|
||||
|
||||
46
docs/operator-nip-fi-policy-client.md
Normal file
46
docs/operator-nip-fi-policy-client.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Operator NIP-FI policy client contract
|
||||
|
||||
Use signed POST and GET `/operator/communities/nip-fi-policy`. The former
|
||||
`/operator/communities/nip-fi-upgrade` route is removed and returns 404 for GET
|
||||
and POST; it no longer performs operator authentication. Deployment proxies
|
||||
may independently reject a request before it reaches the relay.
|
||||
|
||||
For a new intent, read GET `?community_id=<UUID>&expected_host=<host>`. Copy its
|
||||
`expected_policy_generation` and `expected_requires_nip_fi` into the request.
|
||||
These are the only current-state field pair; the response also includes
|
||||
`community_id`, `expected_host`, `mode` and `effective_required`.
|
||||
|
||||
Durably save the complete immutable request before its first possible dispatch:
|
||||
operation UUID, community identity, expected host, authenticated actor and owner,
|
||||
desired `requires_nip_fi`, both CAS preconditions, and the trusted issuer for an
|
||||
enable. Disable omits the issuer. After a crash, timeout or lost response, read
|
||||
GET `?operation_id=<UUID>` or replay that exact saved request with a fresh NIP-98
|
||||
signature. Never reread current policy and overwrite its saved CAS fields under
|
||||
the same UUID. A `policy_conflict` requires a new explicitly authorized intent
|
||||
with a new UUID and freshly observed preconditions.
|
||||
|
||||
POST and operation-status GET return `{ "receipt": { ... }, "mode": "...",
|
||||
"effective_required": true }`. Validate the authenticated operator, saved tuple,
|
||||
desired action, CAS preconditions and positive accepted generation inside
|
||||
`receipt`. Its `current_policy_generation` and `current_requires_nip_fi` describe
|
||||
the current target, not the original intent. They are null after target deletion.
|
||||
`pending`, `applied`, `superseded` and `unavailable` have the meanings in the
|
||||
[operator contract](community-auth-upgrades.md#readback-and-response).
|
||||
Historical acceptance never certifies current policy or fleet-wide socket closure.
|
||||
No response reports `active`.
|
||||
|
||||
Previously accepted enable receipts remain readable through operation-status
|
||||
GET, with desired=true and null CAS preconditions. Recover them as history; do
|
||||
not invent preconditions or convert them into a new write. Reusing their UUID
|
||||
for a new fenced request returns `idempotency_conflict`, even after deletion.
|
||||
Treat only the structured 404 `operation_not_found` from the supported endpoint
|
||||
as an absent receipt. A 404 from the removed route is not evidence that an
|
||||
operation was never accepted.
|
||||
|
||||
Ship migrated callers with mutation dispatch paused before rolling out the
|
||||
route removal. Close the old gate on all old binaries and keep the new gate
|
||||
closed on new binaries. Apply additive schema, complete the whole-fleet rollout,
|
||||
then open `BUZZ_NIP_FI_CHANGES` and resume supported callers. See
|
||||
[rollout](community-auth-upgrades.md#schema-and-rollout). An uncertain operation
|
||||
from the old caller needs explicit reconciliation; a missing receipt is not
|
||||
permission to synthesize a replacement policy write.
|
||||
@@ -5,8 +5,11 @@ stored requirement through the deployment operator API. The command needs the
|
||||
configured `RELAY_OPERATOR_API_ORIGIN` and an allowlisted operator signing key
|
||||
in a protected file. It sends one signed request and never retries automatically.
|
||||
|
||||
Before changing policy, apply the schema and roll compatible management writers
|
||||
across the whole fleet with `BUZZ_NIP_FI_CHANGES=false`. Then explicitly
|
||||
Before rolling, close `BUZZ_NIP_FI_UPGRADES_ENABLED` on every old relay and
|
||||
keep `BUZZ_NIP_FI_CHANGES=false` on new relays. Pause callers and upgrade them
|
||||
for the [supported API](operator-nip-fi-policy-client.md) before deploying this
|
||||
relay version. Apply the schema and roll compatible management writers across
|
||||
the whole fleet. Then explicitly
|
||||
enable that gate, which controls both enable and disable. Enable additionally
|
||||
requires the deployment-selected issuer to be trusted and already bound to the
|
||||
community. The active community must have exactly one owner; the authenticated
|
||||
@@ -71,5 +74,7 @@ A racing sweep may close one last non-FI socket; it can reconnect under optional
|
||||
policy. FI-admitted sockets retain their finite deadline. Re-enable repeats the
|
||||
optional-lease drain and existing periodic revalidation.
|
||||
|
||||
The `nip-fi-upgrade enable/status` command targets the legacy enable-only endpoint.
|
||||
Use the general command for new operations; legacy GET hides general CAS receipts.
|
||||
The old `nip-fi-upgrade` command and endpoint are removed. Requests to that
|
||||
route now return 404. Recover historical receipts with `nip-fi-policy status
|
||||
--operation-id`; their original null CAS preconditions remain visible. Never
|
||||
reuse a historical operation UUID for a new intent.
|
||||
|
||||
Reference in New Issue
Block a user