mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 17:33:20 +02:00
docs(next-gen): iteration-10 review pass — 9 consistency fixes + 'how to use' note
- Phase count: '7-phase' -> '8-phase (Phase 0-7)' in README (x2) + phasing.md (the docs have 8 sections; Phase 0 carries real deliverables). - D16: add the 7th worker job (dashboard rollups) to the parenthetical — added by F2 but never propagated to the ADR. - open-questions.md: bump stale status header iteration 7 -> 9. - testing.md: add the missing infrastructure.md row to the component->layers table; add the missing Phase 6 exit-criteria section (Phase 7 had one). - D06: FastAPI Depends -> Fastify preHandler decorator (Python leftover, D22/F9). - multi-tenancy.md: MqttIngester.__init__ -> TS instanceId constructor arg. - frontend.md: SSE-auth anchor pointed at the parent section, not the auth subsection. - D13: drop the undocumented 'Q-C' sigil. - phasing.md: add a 'How to use this plan during implementation' section orienting implementers to the three-layer structure (component docs / checklist / testing) and stating that per-phase build order + PR-level task decomposition belongs in the implementation repo's tracker, not the design.
This commit is contained in:
@@ -26,8 +26,8 @@ Then read the [decisions](decisions/) for the locked architectural choices, and
|
||||
| [review-findings.md](review-findings.md) | Iteration-8 design-review findings (13 issues) and their resolutions, cross-referenced (F1–F13) into the docs below |
|
||||
| [overview.md](overview.md) | Current system inventory, pain points, target architecture principles, topology |
|
||||
| [code-warts.md](code-warts.md) | Catalog of 52 antipatterns and gotchas from the current codebase (lessons for the new repo) |
|
||||
| [phasing.md](phasing.md) | The 7-phase plan, risks, and design retrospective (what shifted across iterations) |
|
||||
| [implementation-checklist.md](implementation-checklist.md) | Single-page actionable checklist for every task across all 7 phases |
|
||||
| [phasing.md](phasing.md) | The 8-phase plan (Phase 0–7), risks, and design retrospective (what shifted across iterations) |
|
||||
| [implementation-checklist.md](implementation-checklist.md) | Single-page actionable checklist for every task across all 8 phases (Phase 0–7) |
|
||||
| [testing.md](testing.md) | Test strategy (vitest + Playwright pyramid, D23), D5 benchmark plan, and phase exit criteria |
|
||||
| [open-questions.md](open-questions.md) | All resolved — 2 deferred measurements remain (D5 benchmark, D8 compression check) |
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ bootstrap().then(({ config, me }) => {
|
||||
|
||||
## SSE-driven live pages
|
||||
|
||||
A single `useEventStream` hook connects to the SSE endpoint and patches the TanStack Query cache. The browser's native `EventSource` authenticates via the session cookie (same-origin, sent automatically) — no custom headers needed, because the web tier proxies the SSE connection and injects the JWT upstream (see [api.md → SSE Auth](api.md#realtime-sse)).
|
||||
A single `useEventStream` hook connects to the SSE endpoint and patches the TanStack Query cache. The browser's native `EventSource` authenticates via the session cookie (same-origin, sent automatically) — no custom headers needed, because the web tier proxies the SSE connection and injects the JWT upstream (see [api.md → SSE Auth](api.md#sse-auth-cookie-based-proxy-transparent)).
|
||||
|
||||
```tsx
|
||||
function useEventStream(eventTypes: string[]) {
|
||||
|
||||
@@ -88,7 +88,7 @@ changes** — provided the Phase 0 schema is instance-scoped *including its uniq
|
||||
|
||||
The single-tenant assumption lives in exactly **three places** that Phase 7 modifies:
|
||||
|
||||
1. `MqttIngester.__init__(instance_id=...)` — becomes a routing table lookup.
|
||||
1. `MqttIngester`'s `instanceId` constructor arg — becomes a routing table lookup.
|
||||
2. `AuthMiddleware.instance_id` (from env) — becomes hostname/JWT resolution.
|
||||
3. OIDC config (Tier-1 env vars) — gains a per-instance DB path.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Today's auth is two overlapping planes with an implicit trust boundary (S1): dir
|
||||
**Short-lived JWT issued by the web tier, verified at a single API middleware.**
|
||||
|
||||
- **Web tier** mints an access JWT (default HS256 signed with `JWT_SESSION_SECRET`; optional RS256 via `JWT_SIGNING_ALG=rs256` + `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` PEM paths) carrying `sub`, `roles`, `role_tier` (pre-resolved), `instance_id`, `type=access`, `iat`, `exp` (+5 minutes), `jti`. Stored in the existing signed `meshcore-session` cookie (7-day sliding renewal via `jose` JWS — same mechanism, updated library). Every login path (local password, OIDC callback — see D12) converges here.
|
||||
- **API** verifies the JWT at a single `AuthMiddleware`; handlers receive a frozen `Principal` (`user_id`, `roles`, `role_tier`, `instance_id`, `channel_indices` pre-resolved) via `Depends`. **No more `X-User-*` header injection** — the JWT *is* the credential.
|
||||
- **API** verifies the JWT at a single `AuthMiddleware`; handlers receive a frozen `Principal` (`user_id`, `roles`, `role_tier`, `instance_id`, `channel_indices` pre-resolved) via the request decorator injected by the `preHandler` hook (see auth.md). **No more `X-User-*` header injection** — the JWT *is* the credential.
|
||||
- **Direct Bearer (API keys)** remain for CLI/automation; they map to a `Principal` with a fixed role at the same middleware.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## Context
|
||||
|
||||
The preserved-config export/import (migration.md) carries data that cannot be repopulated from RF traffic: `user_profiles` + roles, `routes` + nodes + observers, `node_tags`, adoptions, and node identity stubs. The open question (Q-C in iteration 4): are channels in this set? Channels are *borderline* — the `name` and `visibility` tier could plausibly be re-entered by hand, but the `key_hex` is an operator secret **never transmitted over RF**. Without it, the ingester's `ChannelKeyCache` cannot decrypt incoming channel messages, and the parallel-stack validation window (D14) would lose every channel message.
|
||||
The preserved-config export/import (migration.md) carries data that cannot be repopulated from RF traffic: `user_profiles` + roles, `routes` + nodes + observers, `node_tags`, adoptions, and node identity stubs. The open question (iteration 4): are channels in this set? Channels are *borderline* — the `name` and `visibility` tier could plausibly be re-entered by hand, but the `key_hex` is an operator secret **never transmitted over RF**. Without it, the ingester's `ChannelKeyCache` cannot decrypt incoming channel messages, and the parallel-stack validation window (D14) would lose every channel message.
|
||||
|
||||
## Decision
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## Context
|
||||
|
||||
The job manifest (derived-state.md) consolidates six daemon threads into one `DerivedStateWorker` process owning every periodic job (route evaluator, route history, spam rescore, retention, metrics gauges, CAGG health). A single process is a SPOF — a crash stops all derived-state maintenance until restart. The question (derived-state.md → HA): how to provide HA without introducing a separate coordinator service (etcd/Consul) or a clustering framework?
|
||||
The job manifest (derived-state.md) consolidates six daemon threads into one `DerivedStateWorker` process owning every periodic job (route evaluator, route history, spam rescore, dashboard rollups, retention, metrics gauges, CAGG health). A single process is a SPOF — a crash stops all derived-state maintenance until restart. The question (derived-state.md → HA): how to provide HA without introducing a separate coordinator service (etcd/Consul) or a clustering framework?
|
||||
|
||||
## Decision
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Open Questions
|
||||
|
||||
> **Status (iteration 7):** All design questions resolved. The items below are **deferred
|
||||
> **Status (iteration 9):** All design questions resolved. The items below are **deferred
|
||||
> measurements** (decided in principle, final form pending a benchmark) — not open design
|
||||
> questions. See [decisions/](decisions/) for the 23 locked ADRs.
|
||||
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
# Implementation Phasing
|
||||
|
||||
> The 7-phase implementation plan, risks, and explicit non-goals for the MeshCore Hub rewrite.
|
||||
> The 8-phase implementation plan (Phase 0–7), risks, and explicit non-goals for the MeshCore Hub rewrite.
|
||||
> Each phase is independently shippable and backwards-compatible (data migrations provided).
|
||||
> Phase exit criteria are consolidated in [testing.md](testing.md).
|
||||
|
||||
## How to use this plan during implementation
|
||||
|
||||
This plan is a **design contract**, not a project schedule. Three documents carry three different
|
||||
things — read all three for the phase you're on:
|
||||
|
||||
- **[Component docs](components/)** — *what to build*: schemas, contracts, pseudocode, the authoritative design.
|
||||
- **[implementation-checklist.md](implementation-checklist.md)** — *the ordered task list*: one-line checkboxes per phase, each linking back to its component section. Use it as the milestone tracker.
|
||||
- **[testing.md](testing.md)** — *the acceptance gates*: phase exit criteria + the D5 benchmark + the test pyramid (D23).
|
||||
|
||||
What lives **outside** this plan: per-phase build order and PR-level task decomposition — turning
|
||||
"Implement `AuthMiddleware` preHandler" into its ordered sub-tasks and review sequence. That is
|
||||
execution planning: it evolves with the code, so put it in the implementation repo's issue tracker
|
||||
and link each phase to it there. The plan deliberately asserts no timeline or effort estimate
|
||||
([Strategy note](#strategy-note--scope-realism-f13)); a baked-in task breakdown would contradict
|
||||
that posture and go stale on first contact with the code.
|
||||
|
||||
**Suggested per-phase sequence:** (1) read the component doc(s) for the phase, (2) work the
|
||||
checklist in dependency order, (3) author the phase's `### Tests` deliverables alongside the code
|
||||
(D23), (4) satisfy the `testing.md` exit criteria before declaring the phase done.
|
||||
|
||||
## Phase 0 — Foundations (no behavior change)
|
||||
|
||||
- **Run the D5 fold-vs-separate benchmark first** (synthetic data, throwaway TimescaleDB — testing.md → [D5 plan](testing.md#d5-benchmark-plan-fold-vs-separate)). Its outcome decides the `raw_receptions.path_hashes` shape, so it must land **before** the DDL is authored. This was previously (mis)scheduled in Phase 2, which created a circular dependency — the schema was "frozen" in Phase 0 but the benchmark that shapes it ran two phases later (F5).
|
||||
|
||||
@@ -39,6 +39,7 @@ from production auth stays visible.
|
||||
| [frontend.md](components/frontend.md) | | | ✓ | ✓ |
|
||||
| [multi-tenancy.md](components/multi-tenancy.md) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [migration.md](components/migration.md) (export/import) | ✓ | ✓ | | |
|
||||
| [infrastructure.md](components/infrastructure.md) (runtime, shutdown invariants) | | ✓ | | |
|
||||
|
||||
> The exit criteria below are the **acceptance gates** for each phase; some are slow runtime
|
||||
> observations (5-day parallel-stack diff, live-load throughput) that no unit test can reproduce.
|
||||
@@ -98,6 +99,16 @@ from production auth stays visible.
|
||||
- [ ] Settings + Users admin pages functional; feature flags toggle at runtime; branding change propagates to open tabs via SSE within seconds.
|
||||
- [ ] Pages admin page: create/edit/delete custom pages with markdown editor; nav updates reflect changes.
|
||||
|
||||
## Phase 6 — Polish & decommission
|
||||
|
||||
- [ ] Parallel-stack diff clean for 3 consecutive days within the 5-day window (D14); old stack decommissionable (DNS/proxy/MQTT cut over, old containers stopped).
|
||||
- [ ] RLS audit: a cross-instance query returns 0 rows on **every** tenant-scoped table (including route-health tables), verified as the non-owner `meshcore_app` role.
|
||||
- [ ] JWT rotation drill: rotating `JWT_SESSION_SECRET` invalidates in-flight sessions gracefully.
|
||||
- [ ] `npm audit` clean (backend + frontend); no known CVEs in the lockfiles.
|
||||
- [ ] Load test: N observers × M packets/sec holds NATS backlog bounded and IngestWorker throughput ≥ 5× the old single-threaded path.
|
||||
- [ ] Dashboard p95 < 200ms under load (CAGGs effective); route evaluator p95 < 500ms per route at the D5 High shape.
|
||||
- [ ] New-repo `AGENTS.md`/`CONTRIBUTING.md` derived from [code-warts.md](code-warts.md); README/deployment/operator-migration docs updated; Swagger/ReDoc clean.
|
||||
|
||||
## Phase 7 — Multi-tenancy (self-provisioning)
|
||||
|
||||
- [ ] **Self-service registration:** `POST /api/v1/register` creates a fully operational tenant (instance + hostname + settings + admin) in one transaction; the admin is redirected to their subdomain, logged in, with zero CLI/operator action.
|
||||
|
||||
Reference in New Issue
Block a user