3.9 KiB
Implementation plan
Step-by-step build order for Phase 0 (prototype) and the seams that make Phases 1–5 cheap.
The principle: build the contracts first, stub the internals. Every service should exist with a /health endpoint and a minimal real implementation of its interface before any service is "finished". This gives us an end-to-end walking skeleton from week one.
Stage 0 — Foundations (days 1–3)
- Monorepo tooling. pnpm workspaces for JS/TS; uv or poetry for Python; turbo or nx for build graph; pre-commit (lint, typecheck, format).
- Docker Compose dev env. Postgres, NATS, MinIO (S3), Mailhog, all services wired with hot-reload.
- CI skeleton (Gitea Actions): lint → typecheck → unit test → build → publish images.
- Secrets convention.
.env.exampleper service; prod secrets injected by orchestrator. - Shared types package. OpenAPI source → generated TS + Python clients.
Deliverable: docker compose up brings a green dashboard of /health endpoints.
Stage 1 — Identity & session (days 4–7)
services/auth: Google OAuth2 (PKCE), session cookies, short-lived JWTs, refresh rotation. Library-backed (Auth.js or Ory Kratos + Hydra) — we do not roll our own.services/profile: minimalUserrecord; created on first sign-in.apps/websign-in page; gateway verifies JWT.
Exit check: a user can sign in and fetch their own profile.
Stage 2 — Integrations framework (days 8–12)
services/integrationswith a Connector interface:begin_oauth(user) → redirect_urlfinish_oauth(code, state) → StoredCredentialfetch_signals(user, since) → Event[]
- Token vault: column-level encryption (libsodium), key from env or KMS.
- Todoist connector as the first concrete implementation.
- Web "Connect" page: list of connectors, button per connector, callback handling.
Exit check: a user taps "Connect Todoist", completes the OAuth dance, and the integrations service can fetch their tasks on demand.
Stage 3 — Recommender contract (days 13–16)
services/recommenderexposesPOST /recommend {user_id, context} → {tip}.- Policy interface (
Policy.pick(user, candidates, context) → tip). RandomPolicyv0 — fetches candidates fromintegrations(Todoist tasks), returns one uniformly at random.- Tip shape is provider-agnostic:
{id, kind: "todo"|"advice", title, body, source, deep_link, meta}. apps/webtip page: full black, one tip centered, tap = mark done → callback fires to integrations (complete Todoist task) + emits a feedback event.
Exit check: three-page prototype works end-to-end for one user.
Stage 4 — Hardening the prototype (days 17–20)
- Error surfaces (Sentry), structured logs (pino / structlog), trace IDs across services.
- Rate limits + retries on outbound API calls.
- Integration tests: Playwright for the web flow, pact-style contract tests between services.
- Deploy to a single VM via docker-compose + Caddy.
Exit check: Phase 0 milestone closed.
Seams prepared for later phases (do not implement yet, but do not foreclose)
- Event bus. From day one,
integrationsandrecommenderspeak through an async fn that today is an in-process call but will be NATS tomorrow. Keep the signature(event: NormalizedEvent) → void. - Feature store. The recommender accepts a
contextblob; later, a feature service fills it. Do not inline feature lookups inside the policy. - Policy registry.
PolicyFactory.get(name)so A/B and bandit policies slot in without code changes to the gateway. - Python boundary. Recommender is TS today, but its scoring function is isolated — moving to FastAPI in Phase 1 is a file move, not a refactor.
Staffing assumption
Work is parallelizable across ~3 streams: infra/platform, backend services, web app. Each Gitea issue notes which stream and which phase (milestone) it belongs to.