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>
This commit is contained in:
@@ -1,147 +1,240 @@
|
|||||||
# Rose Ash: Two-Week Sprint
|
# Rose Ash: Fortnightly Sprints
|
||||||
|
|
||||||
**The 20-week plan, compressed. Cut everything that isn't code running in production.**
|
**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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What Gets Cut
|
## Sprint 1: Sexp Content + Sexp Wire (Weeks 1-2)
|
||||||
|
|
||||||
| Deferred | Why |
|
### Week 1: Posts become sexp
|
||||||
|---|---|
|
|
||||||
| Rust native client | Needs stable protocol first — build later |
|
|
||||||
| Client-as-node / IPFS / GPU mesh | Vision is solid, build after protocol exists |
|
|
||||||
| Browser extension | sexpr.js in a `<script>` tag works today, extension is optimisation |
|
|
||||||
| Scalability Tiers 1-3 | Tier 0 is enough, scale when traffic demands |
|
|
||||||
| Multi-instance federation | One instance first, federate second |
|
|
||||||
| Sexp routes (`defroute`) | Nice-to-have, Quart decorators work fine |
|
|
||||||
|
|
||||||
**What stays: everything that produces working infrastructure this fortnight.**
|
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.**
|
||||||
|
|
||||||
## Week 1: Kill Ghost, Ship Sexp Content
|
- [ ] Lexical → sexp converter script (one-time migration)
|
||||||
|
|
||||||
### Day 1-2: Ghost Content Migration
|
|
||||||
|
|
||||||
- [ ] Write Lexical → sexp converter script
|
|
||||||
- [ ] Add `body_sexp` column to Post model (Alembic migration)
|
- [ ] Add `body_sexp` column to Post model (Alembic migration)
|
||||||
- [ ] Run converter against all posts, verify HTML output matches
|
- [ ] Run converter against all posts
|
||||||
- [ ] Switch rendering pipeline: `post_data()` reads `body_sexp`, renders via sexp evaluator
|
- [ ] Diff HTML output: Lexical render vs sexp render for every post
|
||||||
|
- [ ] Fix conversion gaps (embeds, cards, special blocks)
|
||||||
### Day 2-3: Native Editor & Uploads
|
- [ ] Switch rendering pipeline: `post_data()` reads `body_sexp`
|
||||||
|
- [ ] Editor save endpoint writes sexp directly to DB
|
||||||
- [ ] Editor save endpoint writes sexp directly to DB (bypass Ghost Admin API)
|
- [ ] Native media upload endpoints (image, audio, files) — retarget from Ghost proxy
|
||||||
- [ ] Native media upload endpoints on blog service (image, audio, files)
|
|
||||||
- [ ] Native OEmbed lookup endpoint
|
- [ ] Native OEmbed lookup endpoint
|
||||||
- [ ] Editor toolbar actions → sexp nodes
|
- [ ] Deploy, verify every page renders correctly
|
||||||
|
|
||||||
### Day 3-4: Membership Decoupling
|
### 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_labels` → `labels`, drop `ghost_id`
|
- [ ] Rename `ghost_labels` → `labels`, drop `ghost_id`
|
||||||
- [ ] Rename `ghost_tiers` → `membership_tiers`, drop `ghost_id`
|
- [ ] Rename `ghost_tiers` → `membership_tiers`, drop `ghost_id`
|
||||||
- [ ] Clean User model (drop ghost fields, add `membership_tier_id`, `membership_status`)
|
- [ ] Clean User model (drop `ghost_id`, `ghost_status`, `ghost_subscribed`, `ghost_note`, `ghost_raw`)
|
||||||
- [ ] Native `subscriptions` table (replace `ghost_subscriptions`)
|
- [ ] Add `membership_tier_id`, `membership_status` to User
|
||||||
- [ ] Wire Stripe directly (Checkout + Webhooks on orders service)
|
- [ ] Native `subscriptions` table replacing `ghost_subscriptions`
|
||||||
|
- [ ] Wire Stripe directly on orders service (Checkout + Webhooks)
|
||||||
### Day 4-5: Newsletter + Delete Ghost
|
- [ ] Native newsletter model + `user_newsletters`
|
||||||
|
- [ ] Email sending via SMTP/SES
|
||||||
- [ ] Native newsletter model + user_newsletters
|
|
||||||
- [ ] Email sending via SMTP/SES (transactional, not Ghost)
|
|
||||||
- [ ] Newsletter templates as sexp → email-safe HTML
|
- [ ] Newsletter templates as sexp → email-safe HTML
|
||||||
- [ ] Post → email campaign workflow
|
- [ ] Post → email campaign workflow
|
||||||
- [ ] **Delete all Ghost code** — ghost/ directory, ghost_db, webhooks, env vars, Docker service
|
|
||||||
|
### 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
|
- [ ] Alembic migration: drop all `ghost_id` columns
|
||||||
- [ ] Rename model files: `ghost_content.py` → `content.py`, `ghost_membership_entities.py` → `membership.py`
|
- [ ] Rename model files: `ghost_content.py` → `content.py`, `ghost_membership_entities.py` → `membership.py`
|
||||||
|
- [ ] Add new `defrelation`s: `post->post`, `market->product`, `calendar->calendar_entry`, `page->marketplace`
|
||||||
|
- [ ] Migrate `CalendarEntryPost` junction → `ContainerRelation`
|
||||||
|
- [ ] Verify: entire platform runs with zero Ghost code
|
||||||
|
|
||||||
### Parallel (Week 1): New Relations + Cart Split
|
### Sprint 2 outcome
|
||||||
|
|
||||||
- [ ] Add new `defrelation`s (post→post, market→product, calendar→entry, page→marketplace)
|
Ghost is dead. Membership is native. New entity relations work. The platform is self-contained.
|
||||||
- [ ] Migrate CalendarEntryPost junction → ContainerRelation
|
|
||||||
- [ ] Scaffold likes service (port 8009)
|
|
||||||
- [ ] Move PageConfig to blog
|
|
||||||
- [ ] Cart becomes thin CRUD + checkout delegation to orders
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Week 2: Sexp Protocol + Client Runtime
|
## Sprint 3: Cart Split + Sexp Pages (Weeks 5-6)
|
||||||
|
|
||||||
### Day 6-7: Internal Wire Format
|
### Week 5: Cart microservices split
|
||||||
|
|
||||||
- [ ] Define framing: length-prefixed sexp over Unix sockets
|
**Deliverable: cart is thin CRUD. Likes, orders, and PageConfig are in their proper homes.**
|
||||||
- [ ] Python `SexpConnection` class: connect, send, receive
|
|
||||||
- [ ] Quart handler: accept sexp requests, return sexp responses
|
- [ ] 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()`
|
- [ ] `fetch_sexp()` — unified replacement for `fetch_data()` + `fetch_fragment()`
|
||||||
|
- [ ] Error model: `(err :code 404 :message "not found")`
|
||||||
|
- [ ] Unit tests for protocol library
|
||||||
|
|
||||||
### Day 7-8: Internal Mesh on Sexp
|
### Week 8: Internal mesh migration
|
||||||
|
|
||||||
- [ ] Blog ↔ relations on sexp protocol
|
**Deliverable: all inter-service communication runs on native sexp protocol.**
|
||||||
- [ ] Events ↔ relations on sexp protocol
|
|
||||||
- [ ] Market ↔ relations on sexp protocol
|
- [ ] Blog ↔ relations on sexp
|
||||||
- [ ] All internal calls on native sexp (HTTP fallback for external)
|
- [ ] Events ↔ relations on sexp
|
||||||
|
- [ ] Market ↔ relations on sexp
|
||||||
|
- [ ] Cart ↔ orders on sexp
|
||||||
|
- [ ] Account ↔ all services on sexp
|
||||||
- [ ] Benchmark: latency/throughput vs HTTP+HMAC
|
- [ ] Benchmark: latency/throughput vs HTTP+HMAC
|
||||||
|
- [ ] HTTP kept as fallback for external/third-party calls
|
||||||
|
|
||||||
### Day 8-9: sexpr.js Core
|
### 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)
|
- [ ] Sexp parser in JS (<5KB gzipped)
|
||||||
- [ ] DOM renderer: sexp tree → DOM nodes
|
- [ ] DOM renderer: sexp tree → DOM nodes
|
||||||
- [ ] `swap!`, `batch!`, `class!` mutation primitives
|
- [ ] `swap!`, `batch!`, `class!` mutation primitives
|
||||||
- [ ] `request!` — fetch sexp from server, apply mutations
|
- [ ] `request!` — fetch sexp from server, apply mutations
|
||||||
- [ ] Component system: `defcomp`, content-addressed localStorage cache
|
- [ ] Component system: `defcomp`, slots
|
||||||
|
|
||||||
### Day 9-10: sexpr.js Integration
|
### Week 10: Integration + caching
|
||||||
|
|
||||||
|
**Deliverable: sexpr.js on every rose-ash page, with content-addressed caching.**
|
||||||
|
|
||||||
- [ ] Rose-ash serves sexp via `Accept: application/sexp` content negotiation
|
|
||||||
- [ ] `<script src="sexpr.js">` on every page — progressive enhancement
|
- [ ] `<script src="sexpr.js">` on every page — progressive enhancement
|
||||||
- [ ] Partial page updates via sexp mutations (no full reload)
|
- [ ] 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
|
- [ ] DevTools: sexp inspector in browser console
|
||||||
- [ ] Content-addressed caching: hash post body, cache in localStorage
|
|
||||||
|
|
||||||
### Parallel (Week 2): Stability + Pages
|
### Sprint 5 outcome
|
||||||
|
|
||||||
- [ ] Sexp page layouts (base, blog post, market product, event calendar)
|
The client speaks sexp. Pages load faster. Navigation doesn't reload. Components cache locally. Tier 1 is live.
|
||||||
- [ ] Fragment endpoints return raw sexp (callers render)
|
|
||||||
- [ ] Unit tests for new protocol library
|
|
||||||
- [ ] AP federation test: sexp activities between two instances (Docker)
|
|
||||||
- [ ] Deploy Tier 0 scalability (DB split, PgBouncer, workers)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## End State: Day 14
|
## Sprint 6: Federation + Stability (Weeks 11-12)
|
||||||
|
|
||||||
| Before | After |
|
### Week 11: AP over sexp
|
||||||
|---|---|
|
|
||||||
| Ghost CMS running alongside Python stack | Ghost deleted, all content native sexp |
|
**Deliverable: two rose-ash instances federate using sexp activities.**
|
||||||
| Lexical JSON in posts | Sexp in `body_sexp` column |
|
|
||||||
| Ghost handles newsletters, membership, Stripe | Native newsletter, membership, direct Stripe |
|
- [ ] Sexp-formatted activities (Create, Follow, Like, Accept)
|
||||||
| HTTP+HMAC between services | Native sexp protocol between services |
|
- [ ] Federation test: two instances in Docker, full activity flow
|
||||||
| HTML fragments (opaque strings) | Sexp trees (structured, filterable, cacheable) |
|
- [ ] Identity verification over sexp (RSA signatures in sexp envelope)
|
||||||
| Full page reload on every navigation | sexpr.js partial updates, content-addressed cache |
|
- [ ] Cross-instance content rendering (sexp posts display correctly on remote instance)
|
||||||
| No client-side sexp | sexpr.js running on every page |
|
|
||||||
| 6 Ghost-related files + Docker service | Zero Ghost code |
|
### 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What "90%" Means
|
## After Sprint 6: The 10%
|
||||||
|
|
||||||
The 10% left after two weeks:
|
These are real projects, not sprint tasks. Build them when they're needed:
|
||||||
|
|
||||||
- Rust native client (Track 8) — needs months of Rust engineering
|
- **Rust client library + native client** — when you want Tier 2 performance
|
||||||
- Client-as-node / IPFS / GPU mesh — needs the Rust client
|
- **Browser extension** — when sexpr.js proves the concept and you want Tier 1.5
|
||||||
- Browser extension — nice optimisation, not essential when sexpr.js works
|
- **Client-as-node / IPFS / GPU mesh** — when the cooperative network has multiple members with hardware
|
||||||
- Multi-instance federation — test with Docker first, real instances later
|
- **Multi-cooperative commerce** — when a second co-op actually exists and wants to federate
|
||||||
- Scalability Tiers 1-3 — premature until traffic demands it
|
- **Scalability Tiers 1-3** — when traffic hits Tier 0 limits
|
||||||
- Full newsletter template library — basic sending works, fancy templates iterate
|
|
||||||
|
|
||||||
Everything a user touches — browsing, buying, posting, subscribing, federating — runs on sexp end to end. The remaining 10% is optimisation and long-term vision.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Daily Rhythm
|
## The Rhythm
|
||||||
|
|
||||||
```
|
```
|
||||||
Morning: AI generates code for the day's tasks
|
Each sprint:
|
||||||
Midday: Test, fix, deploy to dev
|
Monday-Thursday: Build. AI generates, you steer and test.
|
||||||
Evening: Commit, push, verify on dev, plan tomorrow
|
Friday: Deploy to dev, verify, fix what broke.
|
||||||
|
Weekend: Rest or think about next sprint.
|
||||||
|
|
||||||
|
Each week:
|
||||||
|
One deliverable. Finished. Deployed. Working.
|
||||||
```
|
```
|
||||||
|
|
||||||
No PRs, no code review, no standups. One developer, AI tools, `git push`.
|
**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.
|
||||||
|
|||||||
Reference in New Issue
Block a user