Files
oO/PLAN.md

3.9 KiB
Raw Blame History

Implementation plan

Step-by-step build order for Phase 0 (prototype) and the seams that make Phases 15 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 13)

  1. Monorepo tooling. pnpm workspaces for JS/TS; uv or poetry for Python; turbo or nx for build graph; pre-commit (lint, typecheck, format).
  2. Docker Compose dev env. Postgres, NATS, MinIO (S3), Mailhog, all services wired with hot-reload.
  3. CI skeleton (Gitea Actions): lint → typecheck → unit test → build → publish images.
  4. Secrets convention. .env.example per service; prod secrets injected by orchestrator.
  5. 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 47)

  1. 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.
  2. services/profile: minimal User record; created on first sign-in.
  3. apps/web sign-in page; gateway verifies JWT.

Exit check: a user can sign in and fetch their own profile.

Stage 2 — Integrations framework (days 812)

  1. services/integrations with a Connector interface:
    • begin_oauth(user) → redirect_url
    • finish_oauth(code, state) → StoredCredential
    • fetch_signals(user, since) → Event[]
  2. Token vault: column-level encryption (libsodium), key from env or KMS.
  3. Todoist connector as the first concrete implementation.
  4. 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 1316)

  1. services/recommender exposes POST /recommend {user_id, context} → {tip}.
  2. Policy interface (Policy.pick(user, candidates, context) → tip).
  3. RandomPolicy v0 — fetches candidates from integrations (Todoist tasks), returns one uniformly at random.
  4. Tip shape is provider-agnostic: {id, kind: "todo"|"advice", title, body, source, deep_link, meta}.
  5. apps/web tip 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 1720)

  1. Error surfaces (Sentry), structured logs (pino / structlog), trace IDs across services.
  2. Rate limits + retries on outbound API calls.
  3. Integration tests: Playwright for the web flow, pact-style contract tests between services.
  4. 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, integrations and recommender speak 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 context blob; 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.