client_module.created_at was added and stamped in assignModule, and
stalledProjects now correctly excludes no-tick projects whose created_at
is NULL — but the APEX importer (the path that created the real ~300
clients' projects) inserted client_module WITHOUT created_at. Every
imported project with zero ticks was therefore permanently invisible on
the "Onboarding stalled" tile, exactly the population it exists for.
Three parts:
- Stamp created_at in the APEX importer's client_module insert.
- Backfill existing NULL rows from installed_on where known (both
db.ts migrate() for SQLite and migrations-pg.ts for Postgres); never
fabricate a date when installed_on is also unknown.
- stalledProjects now returns { rows, unknownAgeExcluded } instead of a
bare array, so projects excluded for unprovable age are counted, not
silently dropped. GET /projects/stalled responds with
{ ok, projects, unknownAgeExcluded }; UI wiring left to the parallel
agent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P1: every repo, route, scheduler path and test now speaks one async DB
interface (all/get/run/exec/transaction). better-sqlite3 stays underneath
for dev/tests via SqliteDb (statement cache, manual BEGIN/SAVEPOINT
nesting); openDb stays sync so fixtures remain one-liners. Zero behavior
change: 346/346 tests, typecheck clean.
P2: migrations-pg.ts — numbered Postgres migration set (001-init: bigint
paise, integer flags, TEXT ISO dates, TEXT JSON; reminder CHECK born in
final widened form) + schema_migrations runner.
P3: db-pg.ts — pg Pool behind the same interface: ?->$n rewrite outside
string literals, transactions pinned to one pooled client via
AsyncLocalStorage with savepoint nesting, int8/numeric parsed to JS
numbers so paise math is identical on both engines.
Also: reminder upsert INSERT OR IGNORE -> ON CONFLICT DO NOTHING
(portable, same changes-count semantics).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>