Files
mono/docs/masterplan-sprint.md
giles 105f4c4679 Rewrite sprint plan: fit the task to the timescale
Six 2-week sprints, each shipping one or two complete deliverables.
Not 20 weeks crammed into 2 — the right amount of work for the time.
Each sprint is valuable on its own. Stop after any and you've shipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:24:59 +00:00

9.5 KiB

Rose Ash: Fortnightly Sprints

Fit the task to the timescale, not vice versa.

The golden rule of project management. Don't take a 20-week plan and cram it into 2 weeks — that's denial. Ask instead: what's the highest-value thing you can finish — not start, finish — in two weeks?

Each sprint ships one or two deliverables. Both complete, both deployed, both making everything after easier. The master plan is still the map. You walk it two steps at a time.


Sprint 1: Sexp Content + Sexp Wire (Weeks 1-2)

Week 1: Posts become sexp

Ghost still runs for membership and newsletters. Don't touch that. Just the content path.

Deliverable: every post is sexp in the database, rendered through the sexp evaluator, editable through the existing editor.

  • Lexical → sexp converter script (one-time migration)
  • Add body_sexp column to Post model (Alembic migration)
  • Run converter against all posts
  • Diff HTML output: Lexical render vs sexp render for every post
  • Fix conversion gaps (embeds, cards, special blocks)
  • Switch rendering pipeline: post_data() reads body_sexp
  • Editor save endpoint writes sexp directly to DB
  • Native media upload endpoints (image, audio, files) — retarget from Ghost proxy
  • Native OEmbed lookup endpoint
  • Deploy, verify every page renders correctly

Week 2: Fragment endpoints return sexp

HTTP+HMAC stays as transport. The change is what travels over it.

Deliverable: fetch_fragment returns sexp trees instead of HTML strings. Callers render at the boundary.

  • Relations service returns raw sexp (already renders from sexp — just stop calling to_html())
  • Blog renders relations sexp to HTML at the route level
  • Events renders relations sexp to HTML at the route level
  • Market renders relations sexp to HTML at the route level
  • Callers can now filter, reorder, transform fragments before rendering
  • Measure: is this faster, slower, or same as HTML fragments?
  • Unit tests for sexp fragment round-trip (parse → filter → render)

Sprint 1 outcome

Posts are sexp. Internal fragments are sexp. Ghost still runs but only for membership/newsletters — the content path is clean. Everything that follows builds on this.


Sprint 2: Kill Ghost + New Relations (Weeks 3-4)

Week 3: Membership decoupling

Deliverable: all Ghost membership/newsletter infrastructure replaced with native equivalents.

  • Rename ghost_labelslabels, drop ghost_id
  • Rename ghost_tiersmembership_tiers, drop ghost_id
  • Clean User model (drop ghost_id, ghost_status, ghost_subscribed, ghost_note, ghost_raw)
  • Add membership_tier_id, membership_status to User
  • Native subscriptions table replacing ghost_subscriptions
  • Wire Stripe directly on orders service (Checkout + Webhooks)
  • Native newsletter model + user_newsletters
  • Email sending via SMTP/SES
  • Newsletter templates as sexp → email-safe HTML
  • Post → email campaign workflow

Week 4: Delete Ghost + add entity relations

Deliverable: Ghost is gone. New entity relations are live.

  • Delete blog/bp/blog/ghost/ directory
  • Delete ghost_db.py, web_hooks/, ghost_admin_token.py, ghost_membership.py
  • Remove Ghost Docker service, all GHOST_* env vars
  • Alembic migration: drop all ghost_id columns
  • Rename model files: ghost_content.pycontent.py, ghost_membership_entities.pymembership.py
  • Add new defrelations: post->post, market->product, calendar->calendar_entry, page->marketplace
  • Migrate CalendarEntryPost junction → ContainerRelation
  • Verify: entire platform runs with zero Ghost code

Sprint 2 outcome

Ghost is dead. Membership is native. New entity relations work. The platform is self-contained.


Sprint 3: Cart Split + Sexp Pages (Weeks 5-6)

Week 5: Cart microservices split

Deliverable: cart is thin CRUD. Likes, orders, and PageConfig are in their proper homes.

  • Scaffold likes service (port 8009)
  • Move PageConfig to blog service
  • Orders service owns Order/OrderItem, checkout, Stripe/SumUp webhooks
  • Cart becomes CartItem CRUD + checkout delegation
  • Fragment cleanup: move remaining domain templates out of shared/templates/

Week 6: Sexp page layouts

Deliverable: page layouts are sexp components, not Jinja template inheritance.

  • Base page layout as sexp (head, nav, main, footer)
  • Blog post page layout
  • Market product page layout
  • Event calendar page layout
  • Component composition replaces {% extends "base.html" %}
  • Content negotiation: same route returns HTML or sexp based on Accept header

Sprint 3 outcome

Services are properly bounded. Pages are sexp top to bottom. The Accept: application/sexp header works — the door to client-side sexp is open.


Sprint 4: Internal Protocol (Weeks 7-8)

Week 7: Python sexp client/server library

Deliverable: working SexpConnection class, Quart handler, drop-in fetch_sexp().

  • Wire format: length-prefixed sexp over Unix sockets
  • SexpConnection: connect, send sexp, receive sexp, handle streams
  • Quart integration: handler accepts sexp requests, returns sexp responses
  • fetch_sexp() — unified replacement for fetch_data() + fetch_fragment()
  • Error model: (err :code 404 :message "not found")
  • Unit tests for protocol library

Week 8: Internal mesh migration

Deliverable: all inter-service communication runs on native sexp protocol.

  • Blog ↔ relations on sexp
  • Events ↔ relations on sexp
  • Market ↔ relations on sexp
  • Cart ↔ orders on sexp
  • Account ↔ all services on sexp
  • Benchmark: latency/throughput vs HTTP+HMAC
  • HTTP kept as fallback for external/third-party calls

Sprint 4 outcome

The protocol is real. Running in production. Carrying every inter-service call. Battle-tested on real traffic.


Sprint 5: sexpr.js (Weeks 9-10)

Week 9: Core runtime

Deliverable: sexpr.js parses, renders, and mutates the DOM from sexp.

  • Sexp parser in JS (<5KB gzipped)
  • DOM renderer: sexp tree → DOM nodes
  • swap!, batch!, class! mutation primitives
  • request! — fetch sexp from server, apply mutations
  • Component system: defcomp, slots

Week 10: Integration + caching

Deliverable: sexpr.js on every rose-ash page, with content-addressed caching.

  • <script src="sexpr.js"> on every page — progressive enhancement
  • Partial page updates via sexp mutations (replaces full reload for nav, cart, likes)
  • Content-addressed caching: hash component/post body, cache in localStorage
  • Server sends hash manifest — unchanged content served from cache
  • DevTools: sexp inspector in browser console

Sprint 5 outcome

The client speaks sexp. Pages load faster. Navigation doesn't reload. Components cache locally. Tier 1 is live.


Sprint 6: Federation + Stability (Weeks 11-12)

Week 11: AP over sexp

Deliverable: two rose-ash instances federate using sexp activities.

  • Sexp-formatted activities (Create, Follow, Like, Accept)
  • Federation test: two instances in Docker, full activity flow
  • Identity verification over sexp (RSA signatures in sexp envelope)
  • Cross-instance content rendering (sexp posts display correctly on remote instance)

Week 12: Harden + deploy

Deliverable: everything stable, tested, and deployed.

  • Unit test coverage for all new code (protocol, converter, newsletter, likes)
  • Deploy Tier 0 scalability (DB split, PgBouncer, Hypercorn workers)
  • Bug sweep: end-to-end test every user flow
  • Performance baseline: measure page load, inter-service latency, cache hit rates
  • Documentation: update CLAUDE.md with new architecture

Sprint 6 outcome

Platform is stable, federated, performant, and documented. The protocol has been tested across network boundaries.


After Sprint 6: The 10%

These are real projects, not sprint tasks. Build them when they're needed:

  • Rust client library + native client — when you want Tier 2 performance
  • Browser extension — when sexpr.js proves the concept and you want Tier 1.5
  • Client-as-node / IPFS / GPU mesh — when the cooperative network has multiple members with hardware
  • Multi-cooperative commerce — when a second co-op actually exists and wants to federate
  • Scalability Tiers 1-3 — when traffic hits Tier 0 limits

The Rhythm

Each sprint:
  Monday-Thursday:  Build. AI generates, you steer and test.
  Friday:           Deploy to dev, verify, fix what broke.
  Weekend:          Rest or think about next sprint.

Each week:
  One deliverable. Finished. Deployed. Working.

12 weeks, 12 deliverables, each one complete. Not 20 weeks of partial progress. Not 2 weeks of rushed everything. The right amount of work for the time available.


Sprint Schedule

Sprint 1 (W1-2):   Sexp content + sexp wire          ← content path is clean
Sprint 2 (W3-4):   Kill Ghost + new relations         ← platform is self-contained
Sprint 3 (W5-6):   Cart split + sexp pages            ← services bounded, pages are sexp
Sprint 4 (W7-8):   Internal sexp protocol             ← protocol is real and running
Sprint 5 (W9-10):  sexpr.js client runtime             ← client speaks sexp
Sprint 6 (W11-12): Federation + stability              ← federated and stable

Each sprint builds on the last. Each is valuable on its own. If you stop after any sprint, you've shipped something real.