docs: de-link spike findings from spike/ code (moved to private repo)

This commit is contained in:
vjrj 2026-07-15 20:27:45 +02:00
parent 2d4dec5304
commit 5b64fe14b3

View file

@ -1,9 +1,9 @@
# Spike — Block 2 (social layer) de-risking findings # Spike — Block 2 (social layer) de-risking findings
*Throwaway research spike. Language: English (findings), per code convention. *Throwaway research spike. Language: English (findings), per code convention.
Code lives in [`spike/block2_spike/`](../../spike/block2_spike/) and is marked The throwaway spike code (`spike/block2_spike/`) has been moved out of this
throwaway — **not production, not on the pub workspace, no deps added to Block public repo and preserved privately — it was **not production, not on the pub
1**. This document is the deliverable: what we learned, the risks, and a workspace, no deps added to Block 1**. This document is the deliverable: what we learned, the risks, and a
recommendation on whether/how to take on the social round (Phase 3).* recommendation on whether/how to take on the social round (Phase 3).*
> **Status: this is a spike, not a green light to build Block 2.** CLAUDE.md and > **Status: this is a spike, not a green light to build Block 2.** CLAUDE.md and
@ -34,7 +34,7 @@ See "How to run" at the end.
**Result: works, deterministic, one-way. This unblocks "one identity, one backup".** **Result: works, deterministic, one-way. This unblocks "one identity, one backup".**
- Implemented [`NostrKey.deriveFromSeed`](../../spike/block2_spike/lib/src/nostr_key.dart): - Implemented `NostrKey.deriveFromSeed`:
`HKDF-SHA256(rootSeed, info: "org.comunes.tane/nostr/secp256k1/v1")` → reduce `HKDF-SHA256(rootSeed, info: "org.comunes.tane/nostr/secp256k1/v1")` → reduce
to a valid secp256k1 scalar `d ∈ [1, n-1]` by rejection (bump a counter in the to a valid secp256k1 scalar `d ∈ [1, n-1]` by rejection (bump a counter in the
HKDF info on the astronomically-rare miss, so the result stays a pure function HKDF info on the astronomically-rare miss, so the result stays a pure function
@ -80,13 +80,13 @@ trust do NOT fit behind the same interface — they share a *connection*, not a
*contract*.** *contract*.**
### The seam works (offers) ### The seam works (offers)
- [`OfferTransport`](../../spike/block2_spike/lib/src/offer_transport.dart) = - `OfferTransport` =
`publish(Offer)` / `discover(DiscoveryQuery)` / `retract(id)`. The Nostr `publish(Offer)` / `discover(DiscoveryQuery)` / `retract(id)`. The Nostr
NIP-99 backend ([`NostrOfferTransport`](../../spike/block2_spike/lib/src/nostr_offer_transport.dart) NIP-99 backend (`NostrOfferTransport`
+ [`Nip99Codec`](../../spike/block2_spike/lib/src/nip99.dart)) sits entirely + `Nip99Codec`) sits entirely
behind it. A second backend (ActivityPub/FEP-0837) could replace it without behind it. A second backend (ActivityPub/FEP-0837) could replace it without
the domain noticing. the domain noticing.
- [`Offer`](../../spike/block2_spike/lib/src/offer.dart) is agnostic by - `Offer` is agnostic by
construction: a chosen *summary* + coarse geohash, **no FK into seed tables, construction: a chosen *summary* + coarse geohash, **no FK into seed tables,
no full inventory, no exact address** — exactly the Offer↔Lot split no full inventory, no exact address** — exactly the Offer↔Lot split
[sharing-model.md](sharing-model.md) §2 and [sharing-model.md](sharing-model.md) §2 and
@ -114,14 +114,14 @@ manageable**:
private encrypted DM vs. assert/read a signed certification. Forcing DMs and private encrypted DM vs. assert/read a signed certification. Forcing DMs and
certifications behind `OfferTransport.publish/discover` would overload it. certifications behind `OfferTransport.publish/discover` would overload it.
- **Recommendation — now demonstrated in code, not just asserted:** - **Recommendation — now demonstrated in code, not just asserted:**
[`NostrConnection`](../../spike/block2_spike/lib/src/nostr_connection.dart) is `NostrConnection` is
**one shared** socket + key + sign + REQ/EOSE lifecycle, and **all three thin **one shared** socket + key + sign + REQ/EOSE lifecycle, and **all three thin
interfaces sit on top of the same instance** — interfaces sit on top of the same instance** —
[`OfferTransport`](../../spike/block2_spike/lib/src/nostr_offer_transport.dart) `OfferTransport`
(NIP-99), (NIP-99),
[`MessageTransport`](../../spike/block2_spike/lib/src/nostr_message_transport.dart) `MessageTransport`
(NIP-17) and (NIP-17) and
[`TrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart) `TrustTransport`
(custom WoT). The trust test even runs offer discovery and trust annotation (custom WoT). The trust test even runs offer discovery and trust annotation
over one connection. This is the shape to lift into `commons_core`: shared over one connection. This is the shape to lift into `commons_core`: shared
connection, per-concern contract. Trying to make `OfferTransport` also carry connection, per-concern contract. Trying to make `OfferTransport` also carry
@ -139,7 +139,7 @@ that whole happy path now runs — what's left is production hardening (below).
**Result: the flow works end to end. The real risk is NIP maturity/ecosystem, **Result: the flow works end to end. The real risk is NIP maturity/ecosystem,
not the mechanics.** not the mechanics.**
- Built a hermetic in-process [`MiniRelay`](../../spike/block2_spike/lib/src/mini_relay.dart) - Built a hermetic in-process `MiniRelay`
(NIP-01 subset: EVENT/REQ/EOSE/CLOSE, filter by `kinds`/`authors`/`#g`, (NIP-01 subset: EVENT/REQ/EOSE/CLOSE, filter by `kinds`/`authors`/`#g`,
addressable-event replacement for kind 30402). No network → CI-safe, no flaky addressable-event replacement for kind 30402). No network → CI-safe, no flaky
public-relay dependency. public-relay dependency.
@ -183,13 +183,13 @@ The plan ([network-trust.md](network-trust.md) §4) calls messaging "grande" and
[open-decisions.md](open-decisions.md) §D.3 singled it out as the tightest [open-decisions.md](open-decisions.md) §D.3 singled it out as the tightest
coupling to Nostr. So the spike built it rather than hand-waving: coupling to Nostr. So the spike built it rather than hand-waving:
- **NIP-44 v2 encryption** ([`Nip44`](../../spike/block2_spike/lib/src/nip44.dart)): - **NIP-44 v2 encryption** (`Nip44`):
secp256k1 ECDH → HKDF conversation key → ChaCha20 + HMAC-SHA256 with secp256k1 ECDH → HKDF conversation key → ChaCha20 + HMAC-SHA256 with
length-hiding padding. Conversation key is symmetric (A→B == B→A, tested); a length-hiding padding. Conversation key is symmetric (A→B == B→A, tested); a
wrong key cannot decrypt (throws, tested); short messages pad to the same wrong key cannot decrypt (throws, tested); short messages pad to the same
bucket so ciphertext length doesn't leak plaintext length (tested). bucket so ciphertext length doesn't leak plaintext length (tested).
- **NIP-17 / NIP-59 gift-wrap onion** - **NIP-17 / NIP-59 gift-wrap onion**
([`NostrMessageTransport`](../../spike/block2_spike/lib/src/nostr_message_transport.dart)): (`NostrMessageTransport`):
rumor (kind 14, unsigned) → seal (kind 13, signed by sender, NIP-44 to rumor (kind 14, unsigned) → seal (kind 13, signed by sender, NIP-44 to
recipient) → gift wrap (kind 1059, signed by a **throwaway ephemeral key**, recipient) → gift wrap (kind 1059, signed by a **throwaway ephemeral key**,
NIP-44 to recipient). NIP-44 to recipient).
@ -223,12 +223,12 @@ There is no settled NIP for a web of trust, so — exactly as
anticipates — the spike models **its own WoT, Duniter-compatible**, over Nostr: anticipates — the spike models **its own WoT, Duniter-compatible**, over Nostr:
- **Certifications as events** - **Certifications as events**
([`NostrTrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart)): (`NostrTrustTransport`):
a custom addressable kind (30777) keyed by (issuer, `d`=subject), so one live a custom addressable kind (30777) keyed by (issuer, `d`=subject), so one live
"A vouches for B" per pair; re-certifying renews, revoking replaces (tested). "A vouches for B" per pair; re-certifying renews, revoking replaces (tested).
Certifications expire (Ğ1 semantics) and are public (like the on-chain Ğ1 WoT). Certifications expire (Ğ1 semantics) and are public (like the on-chain Ğ1 WoT).
- **The membership rule is pure and separately tested** - **The membership rule is pure and separately tested**
([`WebOfTrust`](../../spike/block2_spike/lib/src/web_of_trust.dart)) — the (`WebOfTrust`) — the
`commons_core`-worthy piece: Duniter's two rules, **N certifications from `commons_core`-worthy piece: Duniter's two rules, **N certifications from
existing members** (sigQty) and **within distance D of the bootstrap existing members** (sigQty) and **within distance D of the bootstrap
referents** (stepMax), iterated to a fixpoint (becoming a member can push referents** (stepMax), iterated to a fixpoint (becoming a member can push
@ -263,8 +263,9 @@ entity, and optionally importing the on-chain Ğ1 WoT (level 3) as a trust sourc
([open-decisions.md](open-decisions.md) §D.1, §D.6). That is what should drive ([open-decisions.md](open-decisions.md) §D.1, §D.6). That is what should drive
the Phase-3 estimate and the funding ask. the Phase-3 estimate and the funding ask.
4. **Do not start Block 2 from this spike.** It is throwaway research code (not 4. **Do not start Block 2 from this spike.** It is throwaway research code (not
vector-verified, no offline delivery, no persistence, single relay). Delete vector-verified, no offline delivery, no persistence, single relay). The
`spike/block2_spike/` once these findings are absorbed. The funded social `spike/block2_spike/` code has been moved out of this public repo (preserved
privately) now these findings are captured. The funded social
round should **rebuild on vetted client libraries in `commons_core`**, using round should **rebuild on vetted client libraries in `commons_core`**, using
the shapes proven here — one `NostrConnection`, three interfaces, the pure the shapes proven here — one `NostrConnection`, three interfaces, the pure
`WebOfTrust` rule — and spend its risk budget on hardening and bootstrap, not `WebOfTrust` rule — and spend its risk budget on hardening and bootstrap, not
@ -283,11 +284,12 @@ entity, and optionally importing the on-chain Ğ1 WoT (level 3) as a trust sourc
## How to run ## How to run
The throwaway spike code has been moved out of this public repo and is preserved
privately; nothing here was wired into `app_seeds` or `commons_core`'s production
graph, and the Block 1 suite is untouched. For the record, it ran as:
```sh ```sh
cd spike/block2_spike cd spike/block2_spike # in the private archive
dart pub get dart pub get
dart test # derivation · privacy · roundtrip · messaging · trust · web_of_trust (30 tests) dart test # derivation · privacy · roundtrip · messaging · trust · web_of_trust (30 tests)
``` ```
Nothing here is wired into `app_seeds` or `commons_core`'s production graph; the
Block 1 suite is untouched.