[Proposal] Thread sealing: quorum-gated finality for resolved threads
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Problem
Conversations resolve, but the substrate has no way to say so. A decision thread reaches its conclusion and then accretes drive-by replies for weeks, each one reopening a question that was already answered. Clients improvise with :white_check_mark: emoji conventions — nothing enforces them, and nothing records who decided the matter was closed.
The gap is real in the current kind registry: `kind:46030/46031` record a single approver's judgment and have no consequence outside workflow execution. There's no threshold semantic and no notion of finality anywhere in the tree (no VISION doc, NIP, or open issue mentions sealing or quorum — happy to be corrected).
## Proposal (one paragraph)
Threads can be sealed: a `kind:46040` seal request names a thread root, a threshold k, and an explicit approver set (relay-validated against channel membership, snapshotted at request time). Approvers publish individually signed `kind:46041` approvals bound to the request id, so consent is bound to exact parameters. When the relay observes the k-th valid approval, it marks the thread sealed and emits a relay-signed, channel-scoped `kind:8004` delta naming every counted approver — any client can re-verify the quorum from the signed approvals. Once enforced, the relay rejects new writes into the sealed reply tree (`sealed:` OK message). Unsealing is the same grammar in reverse, permanently exempt from the write-block (following the moderation Phase 1 precedent that restriction-lifting paths must never be blockable), with unseal quorum ≥ seal quorum and the unseal approver set drawn from the original approvers ∪ admins. Admin single-approver thread locking is the k=1 degenerate case, not a separate feature.
## State model
```
OPEN ──(46040 accepted)──▶ OPEN + PENDING_SEAL
OPEN + PENDING_SEAL ──(k-th 46041 accepted)──▶ SEALED
OPEN + PENDING_SEAL ──(lapse/expiry)──▶ OPEN
SEALED ──(46043 accepted)──▶ SEALED + PENDING_UNSEAL
SEALED + PENDING_UNSEAL ──(k'-th 46041 accepted)──▶ OPEN
SEALED + PENDING_UNSEAL ──(lapse/expiry)──▶ SEALED
```
PENDING_UNSEAL is a sub-state of SEALED — the write-block never loosens while an unseal quorum accumulates. History is append-only; seal → unseal → seal cycles are legal and fully attributed.
## Why this mechanism
Every team already trusts it daily: GitHub branch protection is relay-enforced quorum — individually attributed approvals, counted by the forge, gating an irreversible-ish action. Deliberately no threshold cryptography: a threshold signature proves *that* k participants consented while erasing *which* k, and the audit question is always "who authorized closing this?" — the answer must be names. The relay is trusted for enforcement, not for attribution: every counted approval is independently verifiable from its signature.
## Precedents followed
* **Handler shape**: structural sibling of `handlers/identity_archive.rs` (user-signed request → relay validation → state table → relay-signed deltas in the 8xxx range)
* **Write-block placement**: adjacent to the ban/timeout gate in ingest, including its key edge case — the lifting path stays writable
* **Kinds** at 46040–46043 in the workflow range; deltas carry `h` and follow the subject channel's read controls
* **Phased rollout** with a numbered-decisions plan doc, mirroring Community Moderation Phase 1: Phase 1 records seals (state + deltas, no enforcement — "recorded, not enforced" per the draft's conformance section), Phase 2 enforces (the ingest write-block, with an explicit by-kind enforcement matrix keyed on resolved thread ancestry, not raw `e` tags)
## Deliberately deferred
Channel/community sealing, role-scoped and predicate quorums (cardinality is framed as the minimal quorum predicate so richer predicates are an extension, not a redesign), a soft "checkpoint" tier, blocking objections (deny votes), and a transparency log. None are blocked or foreclosed by this design.
## Status
Full NIP draft (wire format, validation rules, trust model, invariants, concurrency edge cases) and a design-decisions doc with anticipated Q&A are written and have been through several adversarial review passes — see gist below. If the approach has support, I'll open them as a docs-only RFC PR for detailed review before any implementation.
Related: #54 (these kinds extend the 46xxx approval vocabulary that workflow support established).
Feedback especially wanted on:
1. Is thread-root the right first subject, or would maintainers rather see this land against a different scope?
2. The Phase 1/Phase 2 split — is "record first, enforce second" the right rollout, or should enforcement ship in one PR?
3. Kind number placement (46040–46043 + 8004/8005 following NIP-IA's delta convention) — any collisions with unpublished plans?
Attachments
Gist (both docs): https://gist.github.com/AaronGoldsmith/dbaf3382815b2f10f33b0ef9b0b34f5d
* `NIP-SL.md` — full protocol draft
* `THREAD_SEALING_PLAN.md` — design decisions + Q&A + phased checklist
Contributor guide
Research direction
Start by reading handlers/identity_archive.rs and the ban/timeout gate in ingest, then compare the proposed wire format and validation rules in the attached NIP-SL.md and THREAD_SEALING_PLAN.md. Review related issue #54 and the stated Phase 1/Phase 2 boundaries. Done is a reviewed docs-only RFC PR containing the agreed protocol and design decisions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100