# 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_labels` → `labels`, drop `ghost_id` - [ ] Rename `ghost_tiers` → `membership_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.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 ### 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.** - [ ] `