4 months ago
e47c736A live crash (received_at.toISOString is not a function) exposed a typing lie:
listActivityFeed uses db.execute() (raw SQL — the LATERAL + grp CTE aren't
expressible in the query builder) and typed its rows `as unknown as
{ received_at: Date }`. Raw pg rows are NOT Drizzle-mapped — timestamptz comes
back an ISO string, so the .toISOString() blew up. Fix it properly, not with a
band-aid.
- move the feed wire shapes into @tripwire/contracts/activity.ts as zod schemas
(run summary, timeline entry, group, feed item, feed). they crossed the
server->client boundary and were duplicated in db/services AND web — the drift
class. delete both copies; db + web import from contracts. `pending` is an
explicit optional client-only flag (server never emits it).
- every raw query maps through explicit coercion (mapEntry/mapRun/asMs/asIso/
asString) off Record<string, unknown> — no `as unknown as` on query results.
the one surviving `as` is row.normalized as NormalizedEvent (jsonb validated
on write, §5.6), commented.
- the getActivityFeed server fn now activityFeedSchema.parse()s its output — a
drifted normalized event or mistyped timestamp fails loudly at the boundary.
- integration test on real postgres: listActivityFeed returns a group whose
latestActivityAt is a valid ISO string and whose output parses clean against
the contract, including a standalone (subject_number IS NULL) row.
audit of other web server-fn casts: only rules.functions.ts has two field-level
`as JsonValue` (JSON coercion, not a return-shape cast) — left as is.
Checks: biome clean, typecheck all pkgs 0, boundaries ok, 197 tests.Parent1e50b33