spike(block2): de-risk web of trust — the last big unknown
Adds TrustTransport as the third interface on the shared NostrConnection, completing the social-layer happy path in the spike: - WebOfTrust: pure, Flutter-free Duniter membership rule (N certs from members + within distance D of bootstrap referents), iterated to a fixpoint. The commons_core-worthy piece; TDD'd (threshold, distance cutoff, transitive growth, expired/revoked/self exclusion). - NostrTrustTransport: certifications as a custom addressable kind (30777, keyed by issuer+subject) — certify renews, revoke replaces, certs expire. - Trust filters spam (network-trust §2): seeds certify Alice; Eve stays uncertified; Bob discovers both offers and annotates authors — Alice known, Eve unknown — all over ONE shared connection. Findings updated: WoT risk High -> Medium (policy/bootstrap, not feasibility); overall scope Medium-High -> Medium — all three transport contracts now proven. 30 tests green, analyzer clean, Block 1 untouched.
This commit is contained in:
parent
8a81aaf442
commit
d433748ab5
1 changed files with 68 additions and 27 deletions
|
|
@ -22,8 +22,11 @@ The three open decisions from [open-decisions.md](open-decisions.md) §D.3 and
|
|||
| 2 | Does an `OfferTransport` abstraction hold, without leaking inventory/location? | **Yes — and the "one connection, three interfaces" shape is now demonstrated in code.** |
|
||||
| 3 | Does publish→discover-by-geohash actually work on Nostr NIP-99? | **Yes mechanically; NIP *maturity* is the real risk, not the flow.** |
|
||||
| 3b | Does NIP-17 private, metadata-hiding messaging actually work? | **Yes — prototyped end to end. Risk drops from "unbuilt unknown" to "production hardening".** |
|
||||
| 4 | Does a Duniter-style web of trust work over Nostr, and can it filter spam? | **Yes — certify/discover + threshold-and-distance membership + offer filtering all prototyped.** |
|
||||
|
||||
All of it runs with tests (20 passing). See "How to run" at the end.
|
||||
All of it runs with tests (30 passing). All three transport contracts —
|
||||
offers, messaging, trust — plus the pure trust-graph logic are exercised.
|
||||
See "How to run" at the end.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -112,21 +115,22 @@ manageable**:
|
|||
certifications behind `OfferTransport.publish/discover` would overload it.
|
||||
- **Recommendation — now demonstrated in code, not just asserted:**
|
||||
[`NostrConnection`](../../spike/block2_spike/lib/src/nostr_connection.dart) is
|
||||
**one shared** socket + key + sign + REQ/EOSE lifecycle, and **two thin
|
||||
interfaces already sit on top of the same instance** —
|
||||
**one shared** socket + key + sign + REQ/EOSE lifecycle, and **all three thin
|
||||
interfaces sit on top of the same instance** —
|
||||
[`OfferTransport`](../../spike/block2_spike/lib/src/nostr_offer_transport.dart)
|
||||
(NIP-99) and
|
||||
(NIP-99),
|
||||
[`MessageTransport`](../../spike/block2_spike/lib/src/nostr_message_transport.dart)
|
||||
(NIP-17). A third, `TrustTransport` (WoT), would slot in the same way. This is
|
||||
the shape to lift into `commons_core`: shared connection, per-concern
|
||||
contract. Trying to make `OfferTransport` also carry DMs would have overloaded
|
||||
it — the split is real.
|
||||
(NIP-17) and
|
||||
[`TrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart)
|
||||
(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
|
||||
connection, per-concern contract. Trying to make `OfferTransport` also carry
|
||||
DMs and certifications would have overloaded it — the split is real.
|
||||
|
||||
**Residual risk: medium → low-medium.** The abstraction is right *and* two of
|
||||
the three contracts are now built. The remaining risk is **scope, not
|
||||
feasibility**: "the social layer" is offers + messaging + trust + relays
|
||||
together (§D.1 "indivisible"), and trust/WoT plus production hardening of
|
||||
messaging (below) are still ahead.
|
||||
**Residual risk: low.** The abstraction is right and all three contracts are
|
||||
built. The remaining risk is **scope/hardening, not feasibility**: "the social
|
||||
layer" is offers + messaging + trust + relays together (§D.1 "indivisible"), and
|
||||
that whole happy path now runs — what's left is production hardening (below).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -209,6 +213,39 @@ coupling to Nostr. So the spike built it rather than hand-waving:
|
|||
large" weight.
|
||||
- **Spam/abuse** on DMs leans on the (unbuilt) web of trust.
|
||||
|
||||
## Q4 — Web of trust (the last big unknown)
|
||||
|
||||
**Result: a Duniter-style WoT maps cleanly onto Nostr, computes correctly from
|
||||
discovered events, and filters spam. Built and tested.**
|
||||
|
||||
There is no settled NIP for a web of trust, so — exactly as
|
||||
[g1-integration.md](g1-integration.md) §"WoT propia pero compatible"
|
||||
anticipates — the spike models **its own WoT, Duniter-compatible**, over Nostr:
|
||||
|
||||
- **Certifications as events**
|
||||
([`NostrTrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart)):
|
||||
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).
|
||||
Certifications expire (Ğ1 semantics) and are public (like the on-chain Ğ1 WoT).
|
||||
- **The membership rule is pure and separately tested**
|
||||
([`WebOfTrust`](../../spike/block2_spike/lib/src/web_of_trust.dart)) — the
|
||||
`commons_core`-worthy piece: Duniter's two rules, **N certifications from
|
||||
existing members** (sigQty) and **within distance D of the bootstrap
|
||||
referents** (stepMax), iterated to a fixpoint (becoming a member can push
|
||||
others over the line). Tests cover threshold, distance cut-off, transitive
|
||||
growth, and exclusion of expired/revoked/self certs.
|
||||
- **It filters spam (the payoff, network-trust.md §2):** in a test, three seed
|
||||
members certify Alice; Eve stays uncertified. Bob discovers both their offers,
|
||||
then annotates authors — **Alice is "known", Eve stays "unknown"** and gets
|
||||
deprioritised. Trust and offers run **on the same shared connection**, closing
|
||||
the loop between Q2's architecture and this filter.
|
||||
|
||||
**Residual risk: high → medium.** Feasibility is proven; what's left is *policy
|
||||
and bootstrap*, not code: choosing sigQty/stepMax/expiry (network-trust.md §2
|
||||
"a decidir"), the cold-start referent set (fairs, Ğ1 seed groups —
|
||||
[g1-integration.md](g1-integration.md)), whether a collective can certify as an
|
||||
entity, and optionally importing the on-chain Ğ1 WoT (level 3) as a trust source.
|
||||
|
||||
## Overall recommendation
|
||||
|
||||
1. **Q1 is done enough to lock.** Adopt seed→secp256k1 derivation as specified;
|
||||
|
|
@ -217,17 +254,21 @@ coupling to Nostr. So the spike built it rather than hand-waving:
|
|||
2. **Q2: adopt the "one `NostrConnection`, three interfaces" shape.** Don't try
|
||||
to make `OfferTransport` also carry messaging and trust. Keep `Offer`
|
||||
agnostic and the geohash-coarsening seam exactly as prototyped.
|
||||
3. **Q3: Nostr-first is the right bet, and now two of three contracts are
|
||||
proven.** Offers (NIP-99) and private messaging (NIP-17) both work on the
|
||||
shared connection. The remaining unproven weight is **WoT/trust
|
||||
(NIP-85/custom)** plus **production hardening of messaging** (interop-exact
|
||||
NIP-44, offline delivery, multi-device) — that, not the happy-path flow,
|
||||
should drive the Phase-3 estimate and the funding ask
|
||||
([open-decisions.md](open-decisions.md) §D.1, §D.6).
|
||||
4. **Do not start Block 2 from this spike.** Delete `spike/block2_spike/` once
|
||||
these findings are absorbed. The next concrete de-risking target is the **web
|
||||
of trust** (Duniter-style certifications mapped to Nostr events) — the last
|
||||
large unknown — followed by hardening messaging for real-world delivery.
|
||||
3. **Q3/Q4: Nostr-first is the right bet, and all three contracts are now
|
||||
proven.** Offers (NIP-99), private messaging (NIP-17) and the web of trust
|
||||
(custom, Duniter-compatible) all work on the **same shared connection** — the
|
||||
happy path of the entire social layer runs end to end in this spike. What
|
||||
remains is **not feasibility but hardening and policy**: interop-exact NIP-44,
|
||||
offline/multi-device delivery, and the WoT parameters + cold-start
|
||||
([open-decisions.md](open-decisions.md) §D.1, §D.6). That is what should drive
|
||||
the Phase-3 estimate and the funding ask.
|
||||
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
|
||||
`spike/block2_spike/` once these findings are absorbed. The funded social
|
||||
round should **rebuild on vetted client libraries in `commons_core`**, using
|
||||
the shapes proven here — one `NostrConnection`, three interfaces, the pure
|
||||
`WebOfTrust` rule — and spend its risk budget on hardening and bootstrap, not
|
||||
on re-proving the happy path.
|
||||
|
||||
## Risk summary
|
||||
|
||||
|
|
@ -237,15 +278,15 @@ coupling to Nostr. So the spike built it rather than hand-waving:
|
|||
| Offer transport + privacy seam | **Low** | Abstraction holds; leak-proofing is structural and tested. |
|
||||
| Offer publish/discover (NIP-99) | **Low** | Mechanically proven; mature-ish NIP with real ecosystem. |
|
||||
| Messaging (NIP-17) | **Medium** | Flow + metadata-privacy prototyped & tested. Left: interop-exact NIP-44, offline delivery, multi-device. |
|
||||
| Web of trust (NIP-85/custom) | **High** | No settled standard; genuine design work. The last big unknown. |
|
||||
| Overall scope (§D.1) | **Medium-High** | Social layer is indivisible & large; 2 of 3 transport contracts now proven, WoT + hardening remain. |
|
||||
| Web of trust (custom, Ğ1-compatible) | **Medium** | Feasibility proven (certs + membership rule + spam filter tested). Left: parameters (sigQty/stepMax/expiry) + cold-start, not code. |
|
||||
| Overall scope (§D.1) | **Medium** | Social layer is indivisible & large, but the whole happy path now runs end to end; what remains is hardening + policy, not unknowns. |
|
||||
|
||||
## How to run
|
||||
|
||||
```sh
|
||||
cd spike/block2_spike
|
||||
dart pub get
|
||||
dart test # derivation · privacy · roundtrip · messaging (20 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue