docs(clink): record interop review + spec-conformance pass results

https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS
This commit is contained in:
Claude
2026-06-10 17:40:29 +00:00
parent f4e0bcf73d
commit 61387ba12c
+52
View File
@@ -244,3 +244,55 @@ against a real CLINK service is not automated (needs a device or a
mock/live service). Feasible later via quartz's in-process relay server
(`nip01Core/relay/server/`) plus a mock CLINK responder; flagged but not
built.
---
## Interop review & spec-conformance pass (2026-06-10)
Reviewed against the whole `shocknet/CLINK` ecosystem (Lightning.Pub, clink-sdk,
ShockWallet, Zeus, Stacker News, bridgelet, clinkme.dev) and re-audited every
spec file line-by-line against the code. **Verdict: the consume-only
payer/requestor role is conformant and interoperable; no real correctness bugs.**
**Interop fixes shipped:**
1. Manage `details` single-object responses parse (Jackson
`ACCEPT_SINGLE_VALUE_AS_ARRAY`) — Lightning.Pub returns a bare object for
create/update/get, an array only for `list`.
2. `NOffer.encode()` always emits the price-type TLV (3); decode defaults an
absent/unknown type to SPONTANEOUS. The SDK + bridgelet decoders throw on a
missing TLV 3, so omitting it made our pointers undecodable by JS consumers.
3. `Nip05Parser.parseClinkOffer` accepts bridgelet's flat-string shape as well
as the spec's per-name map.
4. Offer receipts are a parseable primitive (`OfferEvent.createReceipt` /
`decryptReceipt`, `OfferClient.parseReceipt`, `OfferReceipt.isOk`).
5. `ClinkOfferPayer` signs offer requests with an ephemeral key (privacy parity
with the SDK/Zeus/Stacker News). Debits keep the persistent account key
(budgets need a stable app identity).
**Conformance hardening shipped:** `NDebit.parse` rejects a non-32-byte k1;
`DebitClient.requestBudget` validates `frequency.unit ∈ {day,week,month}`;
`OfferClient` caps `description` at 100 chars; `DebitResponse.failureDetail()`
surfaces GFY `range`/`retry_after` in the debit zap error path.
**New tests:** `ClinkWireShapeTest` (golden JSON payloads from the public-domain
specs), a clink-demo/SDK canonical `DEFAULT_NOFFER` vector, plus regressions for
each fix above.
**Three "CRITICAL" review findings were verified FALSE — do NOT re-chase them:**
- *Offer price encoding for ≥2³¹ sats* is correct: `addInt` writes the low 32
bits, which are bit-identical to the unsigned 4-byte BE (proven by
`offerLargePriceRoundTripIsUnsigned`, 3e9 sats). Only ≥2³² loses data, which
the spec's 4-byte field can't represent anyway.
- *Parser "wrongly accepts `nostr:`/`lightning:` wrappers"* is not a violation:
the MUST-NOT-wrap rule governs producing QR (our `encode()` is bare); lenient
decode is Postel-legal.
- *Manage "create shape deviation"* — the spec's inline-create example is the
outlier; Lightning.Pub/SDK/clink-demo all use nested `offer.fields` for create,
which we match.
**Remaining non-bugs (intentional / out-of-role):** we don't reject responses
lacking `clink_version` (Lightning.Pub omits it — rejecting breaks interop); the
offer Payment Receipt UI subscription is unwired (primitive ships); `clink_debit`
discovery and the unrestricted-access debit request are creditor/app-side
features outside the consume-only role; noffer TLV 5 currency is dormant
(unimplemented by the SDK and Lightning.Pub too).