Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/progress/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,5 @@ D-177: Collections — a durable, queryable, TTL-pruned keyed record store for c
D-178: dbQuery script bridge = named Data Sources, not script-supplied connection URLs (design; `docs/design/11-datasources.md`). The unwired bridge declared `dbQuery(driver, connectionUrl, sql, params)` — the script supplies host + credentials inline, which for healthcare means credential sprawl (DB passwords in channel scripts/logs, no rotation), unbounded reach (any host/creds = exfiltration/SSRF with no allowlist), and no stable pool key. Since the bridge is unwired the signature is free to change. Decisions: (1) NAMED DATA SOURCES — an admin defines connection profiles server-side (a new CRUD entity + UI like Resources/Collections); scripts call `dbQuery(dataSourceName, sql, params)`. Credentials never touch scripts, reachable DBs are an allowlist by construction, pool key = data-source id, and it sidesteps URL parsing (a Data Source IS a `ConnectionPool` `PoolConfig` + name + policy). (2) Password ENCRYPTED AT REST via existing `content-crypto` (same CONTENT_ENCRYPTION_KEY as PHI); never logged/returned. (3) READ-ONLY BY DEFAULT, opt-in read-write per source, enforced by the DB ROLE + Postgres `default_transaction_read_only` (NOT fragile SQL-string sniffing). (4) Postgres-only in v1 (reuse `pg`/`ConnectionPool`); thin `DbDriver` interface for later drivers (YAGNI now). (5) `DataSourcePoolManager` singleton keyed by data-source id, invalidated on source update/delete (direct call — no event bus), torn down on graceful shutdown. (6) Safety bounds: per-query `statement_timeout`, `maxRows` cap (fail loud), parameterized-only. (7) No SSRF host-blocking (opposite of httpFetch — internal DBs are the use case; the admin gate is the control). User picked named-data-sources + read-only-default via AskUserQuestion. — 2026-07-13 (branch feature/collections)

D-179: Fixed migration `when`-timestamp ordering so drizzle-kit applies 0009/0010 (and future migrations). Root cause: the hand-written migrations 0007/0008 were registered in `meta/_journal.json` with future-dated round `when` values (1784000000000 / 1784000100000 ≈ 2026-07-14), while 0009 (collections) and 0010 (data_sources) were generated 2026-07-13 with real lower `Date.now()` values. `drizzle-kit migrate` applies journal entries in `when` order and skips any whose `when` ≤ the last-applied — so once 0008 was applied, 0009/0010 were silently skipped (migrate reported "success" as a no-op). Effect: a **fresh install** created neither `collections` nor `data_sources`, and the dev DB was missing `data_sources` (the Data Sources page 500'd on "relation does not exist"). Fix: bumped 0009→1784000200000 and 0010→1784000300000 in the journal so all `when`s are monotonic by idx (verified: a from-scratch `drizzle-kit migrate` now creates collections + data_sources). Reconciled the dev DB by updating the already-applied 0009 record's `created_at` to match, then `db:migrate` applied 0010 cleanly. LESSON: never hand-set a migration's journal `when` to a future timestamp — a later generated migration will get a lower real timestamp and be skipped. Since it is now past those dates, newly generated migrations sort correctly again; this was a one-time skew affecting only 0009/0010. — 2026-07-14 (branch fix/migration-timestamp-ordering)

D-180: RBAC now resolves permissions LIVE from the user's role (single source of truth), replacing the per-user user_permissions snapshot. Root cause of the recurring bug (new permissions like collections:*/datasources:* never reaching existing users, requiring a re-seed): permissions were snapshotted into user_permissions only at user create / role-change, but the enforcement/login paths READ that stale snapshot. Meanwhile UserService.getPermissions already resolved live — two divergent truths. Fix: all four readers (auth.middleware authenticate + optionalAuthenticate, auth.service login, socket.ts) now call `permissionNamesForRole(user.role)` (the same resolver getPermissions uses); removed the dead write path `syncPermissionsForRole` from UserService (create/update no longer touch user_permissions). Effect: adding a permission to a role in code reaches every existing user of that role on their next request — no re-seed, no re-login for server enforcement. The `user_permissions` table is now vestigial (kept to avoid a migration this pass; slated for a drop-migration + scope-column cleanup in RBAC Round 2). Chosen over snapshot+reconcile-on-login (user's call) because one source of truth eliminates the whole staleness class. This is the P0 security/correctness fix; admin-managed role CRUD (roles-as-data) and sub-resource ACLs are deferred to RBAC Round 2 per D-audit plan. — 2026-07-14 (branch feature/quality-pass-1)
4 changes: 4 additions & 0 deletions packages/server/src/db/seeds/run-seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ async function seed(): Promise<void> {
const { seedExampleChannels } = await import('./seed-examples.js');
const exampleDb = drizzle(pool, { schema });
await seedExampleChannels(exampleDb);

// Seed showcase example data for otherwise-empty feature pages
const { seedShowcase } = await import('./seed-showcase.js');
await seedShowcase(exampleDb);
Comment on lines +154 to +156
} finally {
await pool.end();
}
Expand Down
Loading
Loading