refactor: architecture revision — modular monolith, auth-commit, event protobuf, privacy-from-day-0

- ADR-0003: modular monolith for Phase 0 with documented extraction triggers
- ADR-0004: Auth.js + OIDC-shaped boundary; dedicated provider when mobile ships
- ADR-0005: protobuf for events, OpenAPI for HTTP, schema-registry CI gate
- New architecture docs: data-model, metrics (magic proxies), privacy (Phase-0 feature)
- Prime directives updated: privacy-as-feature, modular-by-package-deployable-by-stage
- Roadmap revised: Apple OAuth deferred to M1; web push in M1; k3s intermediate; tip-kind-aware UI
- PLAN updated: Phase-0 deletion endpoint, metrics baseline, compose profiles, import-boundary lint
- License decision in README (ARR with OSS plan in Phase 5)
This commit is contained in:
2026-04-13 14:36:11 +00:00
parent cf4c7a0eb4
commit 7f173f88d3
13 changed files with 449 additions and 133 deletions

View File

@@ -1,13 +1,15 @@
# services/
Backend microservices. Each directory is independently deployable, ships a `Dockerfile`, a `/health` endpoint, and its own `README.md` describing its contract.
Backend modules. Each owns a contract and ships its own `README.md`. In **Phase 0** these are internal packages inside a single Node process (ADR-0003); they extract to their own processes as pressure justifies.
| Dir | Role | Phase introduced |
|---|---|---|
| `gateway/` | BFF for clients; auth check; fan-out to services | 0 |
| `auth/` | OAuth (Google/Apple), sessions, JWT | 0 |
| `profile/` | user profile, preferences, consents | 0 |
| `integrations/` | third-party connectors + encrypted token vault (Todoist first) | 0 |
| `recommender/` | `POST /recommend` — policy-driven tip selection | 0 |
| `events/` | event bus ingress + durable signal store | 1 |
| `notifier/` | push/email/web delivery with quiet-hours | 3 |
| Dir | Role | Phase-0 shape | Extracts when |
|---|---|---|---|
| `gateway/` | BFF for clients; auth check; fan-out | in-proc router | never (stays as the edge) |
| `auth/` | Google OAuth (Apple in M1), sessions, JWT | Auth.js behind OIDC shape | mobile native ships (M3) |
| `profile/` | user profile, preferences, consents | in-proc module | team ownership diverges |
| `integrations/` | connectors + encrypted token vault | in-proc module | credential blast-radius isolation |
| `recommender/` | `POST /recommend` — policy-driven tip selection | in-proc; calls `ml/serving` from M1 | scaling hotspot |
| `events/` | event bus + signal log | in-proc emitter (Phase 0); NATS (M1) | always a library + broker, not a service |
| `notifier/` | push/email delivery + quiet hours | in-proc; **web push in M1** | SLA divergence or mobile push scale |
Contracts that cross module lines (HTTP or events) come from `packages/shared-types/`. In-module imports across modules are forbidden by import lint.

View File

@@ -7,11 +7,14 @@ Third-party connectors and the token vault.
```ts
interface Connector {
id: string // e.g. "todoist"
scopes: string[] // human-readable list shown in consent UI
beginOAuth(user): Promise<{ redirectUrl, state }>
finishOAuth(code, state): Promise<StoredCredential>
fetchSignals(user, since?): AsyncIterable<NormalizedEvent>
// optional write-back, e.g. mark task done
act?(user, action): Promise<void>
// incremental-sync cursor (Todoist sync_token, webhook timestamps, etc.)
// stored in Credential.meta; the connector owns its shape.
act?(user, action): Promise<void> // optional write-back (complete task, etc.)
revoke(user): Promise<void> // REQUIRED: provider-side token revocation on disconnect
}
```

View File

@@ -16,12 +16,14 @@ POST /feedback
## Internals (stable seams)
- **Candidate sources** — pluggable async generators. v0: Todoist tasks via `integrations`. Later: advice library, calendar nudges, health prompts.
- **Context assembler** — merges request context with features (inline now, feature-store later).
- **Policy** — `Policy.pick(candidates, context) → tip`. Registered by name:
- **Feature assembler** — fills the `context` blob (inline in Phase 0; calls feature store from M1). Never inlined into policy code.
- **Policy registry** — `Policy.pick(candidates, context) → tip`. Named entries:
- `random` — v0 (Phase 0).
- `bandit.linucb` — v1 (Phase 1).
- `bandit.linucb.pooled` — v1 (Phase 1). **Global-then-personalize**: pooled features shared across users; per-user residual once data allows.
- `remote` — delegates to `ml/serving` FastAPI scorer (Phase 1+).
- **Shadow hook** — every request optionally runs N shadow policies in parallel and logs their picks + estimated rewards. Promotion from shadow → A/B → launch is a separate, deliberate step (ADR-0002).
- **TipInstance persistence** — every decision writes `context_snapshot` (features seen at decision time). This is what makes offline replay honest.
## Phase 0 goal
`RandomPolicy` only. The service, contract, and seams exist; the brain does not yet.
`RandomPolicy` only. The service, contract, registry, shadow hook, and tip-instance persistence all exist; no ML yet.