docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF) - #5946
docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF)#5946tlongwell-block wants to merge 18 commits into
Conversation
b43aa6f to
4f47aba
Compare
d7ec05c to
6babae7
Compare
themiguelamador
left a comment
There was a problem hiding this comment.
This is a strong decomposition, but several normative contracts are not satisfiable or interoperable as written.
- The byte-exact exit test leaves signed and transport bytes unpinned. Logical JWT claim sets do not determine protected-header/payload JSON octets, and JWS signs those exact encoded octets (RFC 7515). NIP-01 signatures likewise are not unique: BIP-340 accepts auxiliary randomness and explicitly permits alternative valid signing algorithms (BIP-340). The WebSocket challenge, upgrade key/headers, header order, and compact client-message encoding were also absent. Two conforming implementations could therefore produce different valid frames and fail the mandatory exit test.
- The immutable claim tuple omits the inputs that define conformance. Without protocol-document revisions, suite revision, and fixture digest, evidence can be carried across a changed specification while the tuple remains unchanged—the exact stale-evidence outcome this section says it prevents.
- EDGE allocates
0x03and0x04without the contracts its own allocation rule requires. No Git smart-HTTP or Blossom proof specification defines the exact validation, binding, freshness, replay identity/window, or vectors, so those codes cannot yet have stable assigned semantics. - Denial interoperability is incomplete across profiles. EDGE never maps absent, partial, invalid, or unreadable edge evidence to the core response classes; the conformance table omits EDGE/delegation attribution; and row 2 claims one fixture per private condition while aggregating many distinct rejection conditions into one row.
- Delegation has two temporal/type errors. A
not_beforeboundary should be live at equality, while expiry is not; and a positive “delegated maximum” is a duration, not a deadline that can be placed directly in a minimum with absolute deadlines. - Several conformance rules contradict one another. Complete synthetic request bytes are mandatory evidence, but the artifact rule forbids retaining any raw assertion. An
offline-jwtclaim also has no lawfulnot-applicabledisposition for current-status or capability-projection revocation oracles. Token-class positives were ambiguous about which selected policy they run under.
I corrected these issues in 83138b1c4: the exit fixture now supplies exact signed evidence, challenge and frame bytes; the claim tuple binds every governing revision; unallocated EDGE codes remain unallocated; denial mappings are closed; synthetic fixture evidence has a narrow exception; delegation boundaries and lease arithmetic are explicit; and optional-oracle applicability is complete.
Verification:
- all three EDGE vectors reproduce their documented pre-MAC SHA-256, raw HMAC-SHA-256, and unpadded base64url MAC exactly
- the six symbolic core
DENY(...)values match the conformance enumeration - every local Markdown link in the NIP-FI document set resolves
git diff --checkpasses
Metadata follow-up: the PR description still promises a worked wire example that is not present, and its Status section says “Draft” while the GitHub PR is marked ready. Please either add that artifact/update the acceptance wording and status, or mark the PR draft again.
…rofiles) Pins the file layout for the approved 9/9/9/9 blueprint: a ~350-line normative core plus separately claimable EDGE / LIFECYCLE / DELEG / CONF profiles and a non-normative model companion. Slice owners replace stub content; the skeleton exists so parallel authors share one layout. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Defines what counts as evidence that an implementation has the behavior core and the profiles require: the immutable claim unit, evidence and anti-evidence rules, the complete per-private-condition denial fixture enumeration, mutation adequacy, and the two-implementation interoperability exit test. Three rules carry the weight. Denial fixtures are enumerated per private condition rather than per public class, because a per-class suite compares a class against itself and passes trivially. Mutation adequacy requires each normative requirement to have a singly-applied, attribution-checked, reachability-witnessed mutant killed by its named oracle, because naming an oracle proves a requirement is claimed, not that the oracle can fail. The exit test requires byte-exact request and per-class denial bytes produced from the documents alone by two independent implementations. FI-TRACE-TOFU-THEFT takes a configuration witness only: under the private posture ruling no discovery output distinguishes enrollment mode, so a discovery witness for that oracle cannot exist. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
The denial-fixture enumeration named four core denial symbols; core now denies six. Correct the count and names, reattribute binding_required to core, and widen row 9 to cover any unrecognized enrollment policy, which core's terminal else also catches. Add enumeration-agreement checks so a future core symbol cannot silently falsify the sentence again, including two that guard the prose-only allowlist itself: promoting an allowlisted condition to a real symbol satisfies the attribution check rather than failing it, so only a minimality check makes that rot visible. Replace the interoperability compared object with what core pins: status, body, and the values of only the header fields core names. Comparing ordered header names failed every conforming pair. State that the anonymity comparison stays wider, so a private reason code in an unnamed header is still a defect. Parameterize the exit test by a shared fixture. Independence is a claim about code, not inputs: two implementations given different issuers, keys or clocks cannot produce equal bytes however correct both are. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Two of the seven enumeration checks were red on the conforming document. Check 7 compared a set of rows against a set of symbols, and two core-attributed rows name no symbol, so it could not pass at any head. Check 5's head clause admitted the one row its own qualifier excluded. A check that is red on a conforming document detects nothing: it cannot be observed to flip, so every mutant reads as caught. Both are corrected and the requirement to run the checks green before scoring a mutant is now written where the checks live. Address the allowlist and the anonymity set by symbol instead of row number. This table is required to grow, and a profile appending one row renumbers the positional references: the allowlist would name the wrong condition and a new denied condition would fall outside the byte-identity requirement without any check firing. State the three readings the compared object left to convention. An independently written conforming implementation diverges on each by language default: a canonicalizing HTTP library emits Www-Authenticate, a server that sets no Content-Length frames the body as chunked, and a JSON encoder spaces its separators. Header names match case-insensitively per RFC 9110 Section 5.1, the compared body is the content per Section 6.4, and the Nostr message is compact JSON per NIP-01's serialization rules. Content-Length stays unpinned; framing is the sender's choice and pinning it would widen the object for no privacy gain. An exit test whose result depends on an unstated convention is the same defect as an oracle no pair can satisfy, one layer down. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Three defects from the independent review lap, each fixed by making the text impossible to read two ways: DELEG evidence boundaries used 'live' (undefined) and declared equality at either temporal boundary expired, which is incoherent for a lower bound and contradicted core's own inclusive-nbf/exclusive-exp idiom. State both comparisons explicitly, matching core: now < mandatory_expiry and not_before <= now + skew. The delegated lease deadline mixed a configured duration into a minimum of absolute instants. Anchor it: lease issue instant plus the configured delegated maximum. EDGE allocated proof-transport codes 0x03/0x04 to Git and Blossom profiles that do not exist, violating the table's own allocation rule (a published stable specification with validation, binding, freshness, replay, and vectors). Unassign both; the unassigned range now starts at 0x03. Core's oracle table compressed six pairs of oracle IDs with slash shorthand that has no consistent expansion rule: three pairs read replace-last, three read keep-prefix, so no reader can infer the grammar. Split all six into one row per literal ID. The table now lists exactly 30 rows, one complete ID each, making the oracle set mechanically enumerable as the mutation-adequacy denominator CONF will reference. Every prose citation resolves to a table row; no shorthand remains. Review findings: Wren (DELEG boundaries, delegated maximum, EDGE codes, shorthand ambiguity), Dawn (denominator enumerability measurement). Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
The PR description promises a worked wire example in core; none existed. Add one compact non-normative section: a protected HTTP POST under client-attached with NIP-98 proof and a relevant body, showing how the two evidence fields, body binding, and the admission phases compose on the wire. Byte-exact values remain the province of the NIP-FI-CONF exit fixture; the example elides credentials so it cannot be mistaken for a conformance vector. Twenty-six lines, kept deliberately smaller than the alternative of striking the promise would save in credibility. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
CONF's mutation adequacy said "each normative requirement in core," which admits four readings with a 3x spread. Make the denominator the listed oracle, selected structurally: rows whose first cell is one complete literal oracle ID, in core and each claimed normative profile. Selecting by section title would silently drop EDGE's five oracles, whose table lives under "Discovery and conformance"; core's heading is "Core behavioral oracles" and no two of the tables agree. Verified 30/5/11/7/4 = 57 rows, zero shorthand, zero duplicates, zero orphans, at a8fab1a. The exit test required byte-exact requests from two implementations. ES256 and BIP-340 draw fresh randomness per signature and nothing here pins JSON member order, so no conforming pair could ever pass. Compare signing inputs instead: the NIP-01 serialization for the proof, decoded header and claim set as JSON values for the assertion. Shipping pre-minted bytes would have stopped testing whether an implementer can mint from the document at all. Add the governing document revision and exit fixture digest to the claim tuple: evidence means different things under different text, and a claim that does not name what it was judged against is unfalsifiable once the specification moves. Exempt the synthetic exit fixture from the redaction rule it otherwise contradicts, and give offline-jwt deployments a lawful N/A for the two current-status oracles, which have no witness to test. Subtraction: the enumeration-agreement subsystem hand-copied core's denial symbols into CONF prose, then spent four checks and two rationale paragraphs guarding the copy. Read the set from core instead and the copy's guards have nothing to guard: seven checks collapse to three. Promotion detection is kept by folding disjointness into check 3 rather than restoring a check -- found because a six-mutant matrix showed the reduced set letting a promoted allowlist entry survive, falsifying a sentence I had just written. All six mutants now killed, green on the conforming document. Also fold three paragraphs restating one rule about unpinned header fields into one. Net +24 lines on CONF. The row splits and the two new tuple elements are not recoverable by deleting text that carries a guarantee, and compressing identifiers to reach a target would trade a real property for a number. Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
The denominator rewrite changed the body and left every summary of it behind. Three sites still promised the deleted requirement-level contract: the global-controls paragraph, the release gate, and -- worst -- the FI-CONF-MUTATION row itself, which is one of the four CONF entries the denominator counts. The gate contradicted the body it gates, so the original ambiguity survived inside the oracle that claims to close it. FI-CONF-INTEROP-EXIT likewise still demanded byte-identical requests the section above had already stopped requiring. All four now say listed oracle and compared object. The exit test promised a request on the WebSocket upgrade and defined a signing-input object only for NIP-98. Define the NIP-42 half alongside it, compared against its own transport's NIP-01 serialization, and pin the complete unsigned event fields for both transports in the fixture. The assertion object compared a protected header the fixture never pinned; pin the complete protected-header and claim-set JSON values. Correct a false premise: signatures were said to differ because ES256 and BIP-340 each draw fresh randomness. Deterministic ECDSA and fixed-aux BIP-340 are conforming and need not differ. The conclusion is unchanged -- conforming implementations may disagree on octets, so an object requiring equal octets fails conforming pairs -- but the reason now matches the algorithms as specified rather than as commonly configured. Give FI-TRACE-CAPABILITY-REVOCATION a lawful N/A. Core makes it conditional on a configured revocation-bounded external projection, while Applicability required every unlisted oracle, so a deployment with no such projection had no disposition it could honestly claim. Same defect class the offline-jwt fix closed for the current-status oracles, one oracle over. All findings are Wren's, verified against the cited lines before editing. A rewrite that changes a contract has to grep for every restatement of the old one; the body moved and the summaries did not, which is exactly the drift this document exists to make impossible. Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
The non-normative worked example still promised that the NIP-FI-CONF exit fixture pins complete byte-exact values. After the compared-object repair, CONF deliberately does not pin byte-exact minted requests: it pins the request compared objects (signing inputs and semantic JSON values) and excludes signature octets. Restating the removed contract is exactly the stale-restatement defect this batch closed; align the phrase. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
d541a73 to
2511d1b
Compare
…on contradiction Review findings from Mari and Quinn on #5946 at 2511d1b: - Repoint the pinned NIP-42/NIP-98 source links from a nonexistent upstream commit (404) to the resolved commit 6d2979b3f503a8539c983efbcdcf901bbcf9ed23 (both 200). - NIP-FI-EDGE no longer classifies transport_contract_id as interoperability-critical, which contradicted core's exclusive deployment-local classification. The value stays deployment-local; the contract semantics EDGE contributes to it are what is normative. - Core's rejection-and-privacy absolute ("never of private server state") now carries the authorization_unavailable carve-out its own table and NIP-FI-CONF already relied on. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
What
Fresh, comprehensive NIP-FI against
main: one small normative core plus four separately claimable profiles, replacing the single 944-line document structure of #3726 (which was based on #1485's branch, notmain).NIP-FI.md(core, ~350 normative lines target) — issuer-qualified identity(iss, sub), independent Nostr proof, client-attached assertion, partial bijection with durable tombstones, atomic final admission, bounded leases, private denials with a closed response vocabulary, retire/revoke/rotate, two contract identities (assertion_policy_id,transport_contract_id), declared freshness class (offline-jwt|current-status), server-declared body authorization relevance (NIP-98 payload-binding fix), BCP 14, "equivalent" defined, worked wire example. FI-INV-01..16 move here as normative text.NIP-FI-EDGE.md— trusted-edge surface: registered adapters,trusted-proxy-hmac-v2envelope + canonicalization,authorization_domain_idderivation (exact 16 RFC 9562 UUID bytes),proof_transport_coderegistry + extension procedure, body-acquisition bounds, normative test vectors. Header-trust-without-provenance is nonconformant.NIP-FI-LIFECYCLE.md— recover / re-enable / provisioned /binding_not_after/ pending-replacement lineage, one conformance trace per privileged transition.NIP-FI-DELEG.md— delegated agents, semantics unchanged, own traces.NIP-FI-CONF.md— claim tuple + evidence rules, 12-row denial mapping with closed public-response vocabulary, byte-identical-response traces, mutation adequacy (one mutant per core MUST), interop exit test.NIP-FI-MODEL.md— demoted to a non-normative companion only after the invariants land in core.Why
The prior draft rated 9 (soundness) / 6 (minimalness) / 7 (elegance) / 7 (correctness) in adversarial + comparative review. This restructure is the approved 9/9/9/9 blueprint: the two-invariant spine stays untouched; everything else becomes claimable profiles; five stacked versioning mechanisms collapse to two contract identities; every core MUST becomes falsifiable by a named trace.
Resolved product calls (owner-approved):
key_mismatch → evidence_rejectedjoins the denial anonymity set.current-statusdeployments may advertise an unconditional residual-revocation bound;offline-jwtadvertises unbounded/unknown, with "revocation stops new issuance" as a deployment prerequisite. Access tokens keep RFC 9068at+jwt;nip-fi+jwtis reserved for a separately minted Buzz assertion.Acceptance bar
Status
Draft while slice owners land their sections (core boundary/idiom, transport vectors, lifecycle traces, correctness kit). Converts to ready when all sections are in and the acceptance bar above is verified.
Supersedes #3726 as the spec vehicle; #1485 remains the design-history anchor.