SHIP and SLAP Root-Host Output Eviction Coordination
Nodes hosting service advertisements on a Bitcoin-based network had no standard way to tell another host "stop showing this one listing" without either host silently ignoring the request or wrongly treating it as a command it must obey. This made it hard to clean up stale, withdrawn, or locally unwanted listings while keeping each host fully in charge of its own data. Now operators can send a verifiable, attributable request to suppress or later restore one exact listing, and the receiving host decides and records the outcome under its own rules.
Reference for an AI
Everything an assistant needs to answer questions about BRC-199 accurately, including what it depends on.
Summary
- Why
- Hosts serving SHIP and SLAP advertisements had no shared, attributable way to request that one specific stale or withdrawn listing stop being served without one operator being able to dictate another's data.
- What
- BRC-199 defines a signed request and response format for asking a root host to suppress or restore serving of one specific SHIP or SLAP advertisement output, under that root's own local policy.
- How
- An operator sends a BRC-192-signed suppress or restore request naming an exact outpoint and evidence to a root's coordination endpoint, and the root evaluates it under its own local policy, commits a durable tombstone before reporting suppression, and returns a signed result that other peers cannot override.
What this lets you do
- Ask a root to stop serving one specific SHIP or SLAP advertisement output
- Supply evidence that an output was spent, withdrawn, or violates local policy
- Request restoration of a specific earlier suppression
- Check the status of a submitted eviction request and get a signed result
- Audit a root's suppression and restoration history through durable decision IDs
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
The specification
Abstract and status
Root hosts can accumulate stale, withdrawn or locally unacceptable [object Object]/SLAP advertisements. This proposal defines attributable requests to suppress specific advertisement outputs, durable local decisions, replay-safe synchronization of those requests, and explicit restoration. Each receiving root remains responsible for its own policy. A peer's request is not a remote administrative command and cannot spend another operator's UTXO.
This proposed profile uses current BRC-88 SHIP/SLAP formats and BRC-24 lookup services, not historical CHIP/CLAP tokens. It uses BRC-192 encoding/signatures, BRC-194 capability selection and BRC-193 error/transport conventions. It changes neither Bitcoin spend validity nor existing token formats or public lookup defaults.
1. Four distinct operations
An owner revokes an advertisement on chain by validly spending its output. A root can independently stop serving a particular output. A local operator can apply a broader domain/identity policy under its own authority. Public history retention is another concern. These four operations MUST NOT be conflated. This protocol coordinates exact-outpoint serving suppression only; it does not standardize global bans, private-data deletion or a universal root quorum.
The profile is https://bsv.brc.dev/overlays/0199#root-eviction-v1. Its registered coordination service is root-advertisements; exact targets name ls_ship or ls_slap. Requests are admitted only from authenticated identities under a configured local requester policy. An unconfigured peer cannot gain privilege by holding a root advertisement. New deployments default to advisory/manual review, not automatic peer eviction.
2. Request and evidence
POST /overlay/v1/root-evictions/request accepts a BRC-192 signed object of type root-eviction-request:
type EvictionRequest = {
version: 1; requestId: string; requester: Identity;
recipient: Identity; chain: Chain;
issuedAt: U64; expiresAt: U64;
action: 'suppress' | 'restore';
targets: {
service: 'ls_ship' | 'ls_slap'; outpoint: Outpoint;
advertisementDigest: Hex32;
evidence: { kind: 'spent'; txid: Hex32; beef: Bytes }
| { kind: 'owner-withdrawal'; advertisement: Evidence }
| { kind: 'operator-policy'; policy: string; detailDigest: Hex32 };
advertisement: Evidence;
restores?: Hex32
}[];
reason: string
}
The signature signer and authenticated caller MUST equal requester. Forwarding original signed bytes is allowed only through a configured relay policy that separately authenticates the relay and preserves requester attribution; relays receive no extra suppression authority. Recipient MUST be this root's configured identity. Use a separately signed request for another recipient. requestId has BRC-193's constraints; digest("root-eviction-request", body) is the request digest.
Every target chain equals the request chain, which equals the selected root's configured chain. Targets are unique and sorted by (service,txid,outputIndex). advertisementDigest = digest("root-advertisement", {service,outpoint,lockingScript}), with lockingScript the complete verified output script in base64. A receiver checks this against actual BEEF or a retained verified output. A mismatching digest cannot suppress a different record. restores is present exactly for restore and names the precise local suppression decision ID being challenged.
Spent evidence must prove the exact output is consumed by the supplied transaction and satisfy the receiver's declared chain-acceptance policy. Owner-withdrawal verifies the advertisement and requester attribution against its declared advertiser identity under current advertisement rules. It remains an off-chain request for local suppression, never a spend signature. Operator-policy evidence names a locally recognized policy and a digest of the supporting material. Receivers MUST NOT fetch arbitrary requester URLs or infer authority from descriptive reason text. Unavailable evidence is pending or rejected, not assumed true.
The maximum request body is 1 MiB and the list contains 1–64 targets. Requests expire no later than issuedAt plus 86400 seconds; a root can impose a shorter advertised maximum. Local clock policy rejects future-issued or expired new requests. Exact retries of already recorded requests may recover their outcome after expiry. Reusing (requester,requestId) with different bodies is conflict, including a changed recipient or target list. Retain a request fence through the request validity period and every related active decision/recovery obligation.
3. Local decisions and acknowledgement
A root evaluates each target under an explicit versioned local policy, including requester authority, reason, evidence, acceptance threshold, requested scope and restoration rights. It MAY accept some targets and reject others. New decisions under this request MUST be made before expiresAt; a still-pending target expires to rejected with reasonCode request-expired. A subsequent local operator action uses a separate decision record, not the expired peer request as authority. There is no default majority or number of agreeing roots that forces acceptance. The outcome is a signed packet of type root-eviction-result:
type EvictionResult = {
version: 1; requestDigest: Hex32; root: Identity;
policyDigest: Hex32; issuedAt: U64;
outcomes: {
service: 'ls_ship' | 'ls_slap'; outpoint: Outpoint;
actionStatus:'pending'|'rejected'|'applied'|'no-op';
reasonCode:string; decisionId?:Hex32; affectedDecisionIds:Hex32[];
revision:U64; serving: {state:'suppressed'|'eligible'|'unresolved';
revision:U64; blockers:{decisionId:Hex32,policyDigest:Hex32}[]}
}[]
}
All requested targets occur exactly once in the same order. The policy digest is frozen for evaluating this request; if the root must change that policy while outcomes are pending, those targets are rejected with reasonCode policy-changed and need a new request. Status reasons are machine-readable local policy identifiers; they must not leak unrelated private data. decisionId is present exactly when actionStatus=applied. Effective serving status is reported separately and cannot obscure whether the requested action happened. A new decision ID is digest("root-eviction-decision", {root,requestDigest,service,outpoint,revision}); an applied suppression always creates an independent decision, including when another decision already blocks the same output. revision is the root's durable local serving-policy revision, not a global chain sequence. Signing the result attests only that root's actual decision; a sender cannot claim all roots complied.
POST /overlay/v1/root-evictions/status accepts {version:1, requester:Identity, requestId:string}. Only that requester or a configured auditor can retrieve its current signed result. Pending outcomes may evolve under the same request digest; completed decision history is immutable and appended rather than overwritten. Repeated completed requests return the same decisions and cannot repeat side effects. Distinct accepted suppressions can coexist on one outpoint; restoration of one does not remove the others.
4. Serving tombstones and transaction boundaries
Before reporting suppressed, commit a serving tombstone and a durable index-removal intent together, or an equivalent atomic transaction. A tombstone records exact outpoint/service/chain, advertisement digest, decision, requester, policy revision, evidence reference, reason and optional local expiry. All serving paths—including cached results, new lookups, snapshot creation, live feeds and re-admission/reindexing—MUST consult the effective tombstone or a coherently fenced materialization. An index-removal callback succeeding once is insufficient: public GASP or replay must not immediately reinsert the row.
Until downstream indexes are consistent, the serving fence blocks the row. Suppressed is reported only after that fence is effective. BRC-193 subscribers receive a coherent source withdraw with a policy reason; clients must not interpret it as an on-chain spend. An in-flight snapshot opened before suppression cannot continue disclosing a row newly prohibited by local policy: terminate/reset it or apply an access-safe current serving fence and reset its claimed snapshot continuity. Do not silently omit a row while claiming the original exact snapshot remained complete.
Retain public evidence/history as required for verification and auditing. Do not invoke a permanent-erasure hook to implement a reversible suppression unless a separate durable source preserves all required restoration state and the hook's original contract allows that use. Existing outputEvicted implementations may promise permanent removal; a new optional serving-policy companion is the appropriate extension. Existing local admin endpoints remain local and are not exposed as peer-authorized routes.
5. Restoration and reorganization
Restoration is a new locally authorized decision referring to an exact earlier suppression. Before exposing the output, revalidate its advertisement bytes, chain/currentness assessment, configured topic rules and all other active suppressions/bans. A spent output cannot become live merely because a peer withdrew a complaint. Reorganization can invalidate spend evidence but still requires reassessment and a recorded policy decision. Expiry of a tombstone similarly initiates reassessment; it does not blindly restore stale cached rows.
Commit restoration and any live membership event coherently. If another suppression remains, report actionStatus=applied for lifting the named decision and serving.state=suppressed with the remaining blockers. A successful action is distinct from permission to serve. A missing local output is recovered using the mandatory advertisement evidence and finite validation procedure in section 7; unresolved validation stays pending and expires to a retained rejection. Owner publication of a new advertisement outpoint is evaluated on its own merits; an exact-outpoint tombstone does not silently widen to every advertisement from that owner or domain. Broader local bans must be separately visible in the root's policy and cannot be smuggled into this request format.
6. Distribution, failure and conformance
Operators can submit separately bound requests to several selected roots and collect each signed result. Retrying an unavailable root is bounded and idempotent. Request propagation does not use a public transaction graph as a secret-control channel and cannot establish global agreement. Coordination records are distinct from ordinary SHIP/SLAP token synchronization; both strict and federated operators may use GASP independently.
Admission and source adapters MUST check tombstones during concurrent ingestion, replay and restoration. Required tests cover unauthorized peer; wrong recipient/chain; altered request replay; spent evidence mismatch; absent evidence; partial batch acceptance; commit before crash; failed index removal with effective serving fence; cache/snapshot/live consistency; GASP re-admission; multiple suppressions; restoration under another ban; tombstone expiry; reorganization; and new-outpoint publication. Tests must demonstrate at least two independently configured roots making different legitimate decisions, without presenting their divergence as Bitcoin consensus failure.
7. Decision history, evidence and delivery fences
Every target includes advertisement:Evidence, selecting the exact original SHIP/SLAP output, regardless of evidence.kind. A root can therefore evaluate suppression/restoration when its index lacks the row. In owner-withdrawal, evidence.advertisement must select the same output and raw bytes; proof variants may differ but are independently checked. Spent evidence proves actual consumption; operator-policy evidence does not replace the original advertisement. The signed request body, including proofs, is immutable: insufficient history is completed only through configured bounded evidence resolvers or a new signed request ID carrying the additional package. There is no unsigned evidence mutation of an existing request. Pending work terminates before request expiry as applied/rejected, or at expiry as rejected/request-expired. A reorg restoration supplies original advertisement evidence plus the root's freshly selected chain/currentness context, never a stale assertion that a removed block proved unspent.
A restore target must bind to a retained decision on this root, same chain/service/outpoint/advertisementDigest. Knowledge of its ID confers no authority. The root's frozen request policy must explicitly authorize that requester to lift that particular decision (for example the original owner-withdrawal requester, or a configured operator); another root's ID or an unrelated outpoint fails invalid. Each accepted suppress creates its own durable basis even if already suppressed. Applied suppression has affectedDecisionIds=[its new decisionId]. Applied restore creates a new audit decision whose affectedDecisionIds=[the lifted suppression ID], atomically marking that basis inactive. Restoring an already lifted basis is no-op with no new decisionId and affectedDecisionIds=[the requested basis]. Pending/rejected have no decisionId and empty affectedDecisionIds. Duplicate completed requests return the exact saved action outcome/revision, never apply it again.
For A and B independently suppressing X, the deterministic trace is: suppress A → applied(A), blockers[A]; suppress B → applied(B), blockers[A,B]; restore A → applied(RA), affected[A], blockers[B]; restore B → applied(RB), affected[B], blockers[] and eligible only after successful currentness/rule validation. Replaying restore A returns its saved applied(RA), even after B has changed, together with a fresh separately versioned serving assessment. It must not imply RA lifted B. Blockers are sorted by decisionId and carry each suppression's original policyDigest; the result's policyDigest is the frozen request evaluation policy. Current broader local bans must be represented by root-issued durable blocking decision IDs as well, without fabricating a peer request.
The root allocates one checked U64 serving-policy revision per committed transaction that changes any decision, pending disposition or effective serving assessment. All affected outcomes in that transaction receive that revision; several targets can share it. Partial evaluation at different times allocates later revisions for only those new decisions. A no-op/replay preserves the original action revision. Initial pending records use the current revision; their later terminal change allocates a new one. Counter overflow is limited before effects. Policy changes preserve completed actions and reject pending ones under the old frozen digest with policy-changed. Expiry/reorganization reassessment is a separate root-authorized audit transition, not retroactive editing of a signed action. Status signs a new envelope/issuedAt with immutable saved action outcomes plus current serving snapshots, each with their own revision; a historical signed result remains evidence of its observation time, not today's serving state.
Require issuedAt < expiresAt, checked expiry arithmetic, the selected maximum lifetime and a finite configured future-clock allowance. Unknown/unauthorized status or replay returns not-found under current access policy, even when the signature is valid. Results are bounded by 1 MiB and contain all targets exactly once in signed request order; blockers are capped by configured active-decision limits. Reject a new operation before effects if its future auditable outcome cannot fit the result budget. Active suppressions, lifted-basis history needed for further restores, request semantic fences and result revisions remain durable for the service identity lifetime. Compact evidence bodies may be reclaimed only when no active decision/restore/audit obligation depends on them; retain IDs/digests/outcomes and never reuse the request key.
Serving has a single linearization gate shared by lookup/cache/snapshot/live/GASP-to-query adapters. Build/hydrate a candidate response, then under a coherent read fence compare the effective policy revision and current authorization, validate every candidate row against current tombstones, and commit the complete bytes to the transport's send queue before releasing that fence. Suppression commits obtain the corresponding write fence. Bytes queued before suppression cannot be recalled; no response queued after suppression may newly disclose the row. Network transmission time does not redefine this boundary. A cache entry or already-hydrated BEEF cannot bypass the gate. Suppression concurrent with admission still wins at serving: admission may retain valid history but must not restore membership across the fence.
A retained live log's old output is subject to current prohibition, just like a snapshot. If replay would disclose a prohibited row, return reset-required before queuing those bytes; do not filter it while claiming uninterrupted replay. A later withdrawal alone does not authorize replay of the forbidden earlier output. New snapshots start after the suppression and exclude it. GASP may synchronize public transaction history under its own policy but cannot repopulate a suppressed serving view. Restoration first verifies evidence/currentness, lifts only its named basis, applies all remaining blocks, and commits eligibility plus a coherent live membership event; failing revalidation leaves serving unresolved/suppressed. Adapter qualification must exercise this actual gate through cache, hydration, retained-log replay and reindex, not only an in-memory set of decisions.
Root suppression and restoration remain source-serving policy, distinct from the BRC-192 reconciled Bitcoin graph. Restoring a still-relevant advertisement may restore this root's membership assertion, but cannot make a verified spent output current. A source withdrawal cannot erase a spend edge or another root's independent decision. Preserve the generation/sequence fences when these decisions feed BRC-193 subscriptions.