Skip to content

docs: ADR-0001 contract sharding architecture#21

Merged
iduartgomez merged 2 commits into
mainfrom
claude/raven-issue-8-breakdown-OnGFB
May 23, 2026
Merged

docs: ADR-0001 contract sharding architecture#21
iduartgomez merged 2 commits into
mainfrom
claude/raven-issue-8-breakdown-OnGFB

Conversation

@iduartgomez
Copy link
Copy Markdown
Contributor

Summary

  • Establishes docs/adr/ and adds ADR-0001: Contract sharding architecture.
  • Records the decision to partition contracts by (write-authority × churn × audience) into three contract types — user shard (per user, owner-writes), thread shard (per root post, anyone-appends, lazy), inbox shard (per user, anyone-appends, owner-prunes).
  • Captures the rationale (why not a single per-user contract with field-level authority), the bounded-state/windowing requirement, and the delegate outbox-with-prune-on-ack pattern.

Decisions recorded

Notes

This is a documentation-only change (no code). It front-runs the schema-changing workstreams under #8 (#11/#12/#13) so they build against a recorded architecture, and it pairs with the migration system in #20.

Closes #16
Closes #18

https://claude.ai/code/session_01BkBKy3SfhdwqFMrv9KGnmr


Generated by Claude Code

Records the decision to partition contracts by
(write-authority × churn × audience) into three types — user shard,
thread shard, inbox shard — resolving the per-user-vs-global question
(#16) and thread-storage question (#18). Leaves fanout strategy (#19)
open.

https://claude.ai/code/session_01BkBKy3SfhdwqFMrv9KGnmr
@sanity
Copy link
Copy Markdown

sanity commented May 23, 2026

Some feedback on the ADR.

The (write-authority × churn × audience) framing is good, and the inbox/thread split it produces is the right call. The framing of "authority enforcement isn't the reason to split, coupling of replication/subscription/churn/migration is" is the load-bearing insight and worth defending. Three substantive pushbacks and a few smaller things.

1. Make the archival scope split explicit

"Dated archive shards... possible later addition, not part of v1" reads as deferred Raven work, which invites the question "what happens to my old posts?" Cleaner framing: durable long-term history is not Raven's job. It's handled by an external discovery/indexing layer (Atlas-style, https://github.com/freenet/atlas/blob/main/PROPOSAL.md). The user shard's ~200-post window is the steady state, not a placeholder until archive shards arrive. One sentence saying so in the ADR closes the question.

A constraint follows for #11/#12 if we take this route: post objects need stable, externally-referenceable IDs, and need to be self-contained signed records. Not because Atlas specifically needs them, but because the pattern of (owner-writes producer contract) → (external indexer / archiver / aggregator) is going to recur across Raven, Atlas, and probably future apps. If Raven posts are self-contained signed records with stable IDs, every downstream consumer benefits; if they're internal-only embedded objects, every downstream consumer has to scrape and rehydrate. The cost of getting this right at #11/#12 time is much lower than fixing it later.

2. Likes belong on the thread shard, not the user shard

The ADR currently puts likes in both places: "likes-given" in the user shard, and "likes... targeting that post" in the thread shard. That's double-bookkeeping, with consistency risk if one write succeeds and the other doesn't.

The thread shard is the right home by the ADR's own axis. A like is "anyone-writes, signed, targets a post," which is exactly the shape used to justify thread-shard storage for replies and quote refs. Removing likes-given from the user shard also cleanly resolves a churn mismatch I was about to flag (high-frequency likes alongside near-static profile data) without needing to introduce a new split.

The "what posts has X liked?" view, if wanted, is a natural delegate-local materialized view (the same pattern the ADR already uses for the timeline) or an Atlas-style discovery query over public thread-shard data. It doesn't need to be replicated state on the user shard.

3. "Anyone-writes with a valid signature" needs a Sybil-resistance story

A signature authenticates the writer but doesn't constrain who can be a writer. With cheap keypair generation, "valid signature" is a placeholder, not a policy: thread shards and inbox shards are both wide open to Sybil flooding. Per-writer rate limits in update_state only help if writers are scarce.

GhostKey (https://freenet.org/ghostkey/) is the natural Freenet-ecosystem fit. Blind-signed credentials backed by a donation, anonymous-at-issuance and pseudonymous-at-use, contract-verifiable. The thread shard's update_state checks the GhostKey cert; no cert, write rejected. Per-writer rate limits become meaningful because each writer cost real money to bring into existence. Tiered privileges by donation amount give a clean knob later (a higher-cert key gets a higher rate cap on inbox writes, for example) without committing to the scheme now.

The ADR doesn't have to pick the final mechanism, but it should name the abuse model and identify a candidate defense. Otherwise "anyone-writes with valid signature" is hiding the most important unresolved design question in the public-write surfaces. This applies equally to thread and inbox shards, and the answer should be consistent.

Smaller things

  • Thread shard created on first like is heavy. Spinning up a whole thread contract for a single like on an otherwise-uninteracted post is a lot of contract for very little state. Reply-only lazy creation seems lighter; likes on a no-reply post can live in the liker's delegate-local view and surface via Atlas-style aggregation later, or wait for the thread contract to exist before being recorded.
  • Window enforcement should be a post-merge truncation rule, not a pre-write check. Concurrent appends from different replicas can otherwise blow the ~200 bound at merge time.
  • Quote-post back-references should go through the inbox shard, not be skipped. That is exactly what inbox is for ("anyone-writes notification to a user"). Skipping the back-reference is a real UX regression vs. Twitter, and it is solvable with a mechanism the ADR already introduces.

Not raising

Bundling profile + recent posts + follows together in the user shard genuinely does fit the axis (owner-writes, low-churn, follower-audience), so that part stands. And the deferral of #19 (fanout-on-read vs. fanout-on-write) is reasonable; the mechanism is in place either way.

[AI-assisted - Claude]

Incorporates sanity's review on PR #21:
- likes live only on the thread shard (drop user-shard double-bookkeeping)
- name the Sybil abuse model for public-write surfaces; GhostKey candidate
- durable history is an external indexer's job (Atlas), not Raven; add
  stable-ID / self-contained-signed-record constraint for #11/#12
- thread shard created lazily on first reply, not on a bare like
- window enforcement as post-merge truncation, not pre-write check
- quote back-references delivered via inbox shard instead of skipped

https://claude.ai/code/session_01BkBKy3SfhdwqFMrv9KGnmr
Copy link
Copy Markdown
Contributor Author

Thanks — all six taken, pushed in d025d5d.

  1. Archival is Atlas's job. Reframed: the ~200-post window is steady state, not a placeholder. Added a "Durable history is not Raven's job" decision pointing at the Atlas proposal, plus the follow-on constraint for Users / identity: profiles, handles, identity delegate #11/Threads / replies: schema, validation, thread view, reply compose #12posts as self-contained signed records with stable, externally-referenceable IDs so downstream consumers reference rather than rehydrate.
  2. Likes → thread shard only. Dropped likes-given from the user shard; added a paragraph spelling out the double-bookkeeping / churn-mismatch reasoning and the delegate-local-view / Atlas-query route for "what X liked."
  3. Sybil model named. New Abuse model (public-write surfaces) section: "valid signature" is a placeholder not a policy; GhostKey named as the candidate (donation-backed, contract-verifiable, makes per-writer rate limits meaningful), consistent across thread + inbox, without committing to the final mechanism.
  4. Reply-only lazy creation. Thread shard now created on first reply; bare likes wait in the liker's delegate-local view until the shard exists.
  5. Post-merge truncation. Windowing is now stated as a deterministic keep-newest-N rule applied after merge, not a pre-write check.
  6. Quote back-ref via inbox. Replaced the skip-the-back-reference decision with delivery through the quoted author's inbox shard (optionally also appended to the thread shard for viewers).

Profile+posts+follows bundling and the #19 deferral left as-is, per your note.


Generated by Claude Code

@iduartgomez iduartgomez merged commit 8a8cf95 into main May 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design: thread storage — reply edges in posts contract vs. separate thread contract Design: per-user posts contract vs. single global feed

3 participants