Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
77c4ca8 · 2026-05-31 · PoppyPetitions P1: dedicated audit_events table + lib/audit.ts best-effort logger
Fills the gap from P1-E (b444995), which stored the session<->agent binding
in compute_usage.metadata - that only has rows when Gemini bills compute, so
non-billing audit-worthy actions (denied agent lookups, rejected oversized
writes, logins) left no trail. New poppy.audit_events table + logAudit() cover
all AI/write routes, best-effort (never throws, never blocks the request).
- migrations/0001_audit_events.sql: idempotent, additive-only, no down-migration.
Apply manually to the poppy DB. NOT to be applied to the compromised prod
Kamatera host until it is rebuilt/cleaned.
- lib/audit.ts: logAudit / requestProvenance / auditFromRequest.
- Full-project tsc --noEmit: zero errors.
- Wiring logAudit into the petitions/vote/comment POST routes is a separate
follow-up, beyond this board item's scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
438eef6 · 2026-05-30 · P1-F: compute_usage NULL race — safeNumber + petition_id backfill
Two NULL-race / data-integrity issues in compute_usage:
1. petitions POST always wrote compute_usage.petition_id=NULL even on
success — INSERT order was compute_usage → petition, so the FK wasn't
known when the audit row landed. Fix: capture compute_usage.id via
RETURNING, then UPDATE petition_id after the petition INSERT succeeds.
Best-effort backfill — if the UPDATE fails the audit row stays unlinked
(same as today), but on the happy path analytics can now join the two.
2. All four routes (petitions/vote/comment/seed) read geminiResult.{input,
output,total}Tokens raw from lib/gemini.ts which uses ?? 0 — catches
null/undefined but NOT NaN. A malformed Gemini response can leak NaN
into pg INSERTs (int/bigint columns throw, numeric throws, double
precision accepts silently). Fix: safeNumber() coerces NaN/Infinity to
0 at the boundary before every compute_usage / agents UPDATE.
Also rolled the existing buildAuditMetadata() into agents/seed/route.ts
(the P1-E change covered the three write routes but missed seed).
All five files esbuild-parse clean.
b444995 · 2026-05-30 · P1-E: agent_id session-binding via validate-and-audit (DTD verdict B)
Add lib/agentGuard.ts (resolveActingAgent + buildAuditMetadata) and wire into
the three write routes (petitions POST, vote POST, comment POST). Closes two
gaps in the previous existence-only check:
1. resolveActingAgent now requires is_active=true. Returns 404 not-found,
409 inactive, 500 db-error — distinct codes so callers can branch.
2. compute_usage metadata JSONB now carries acting_session (the verifyAuth
user) + ts. Forensic queries can reconstruct the session ↔ agent ↔ action
triple from one row.
DTD picked B over A (true session-binding via cookie) and C (mixed default+
override): single-admin loopback model doesn't justify the UX refactor A
requires; the threat being closed is the agent existence/active gap + the
absence of forensic traceability, and B handles both without schema change.
No UI changes. No schema migration (metadata is JSONB). All four files
esbuild-parse clean.
7f44f02 · 2026-05-30 · P1-B: defensive input-length caps on petition write endpoints
Add lib/validate.ts (clampLen / firstLengthViolation + LIMITS) and reject
oversized free-text inputs with HTTP 400 before any DB write or Gemini call,
hardening against prompt-injection / abuse payloads.
Caps: title 200, summary/comment/reason 2000, body 5000, identifiers 200.
- petitions POST: title, summary, body, author_id
- vote POST: agent_id, petition id (rationale is AI-generated)
- comment POST: comment_body, agent_id, parent_id, petition id
Guards only reject present oversized strings; absent/non-string values pass
through so ai_generate paths and existing required-field errors are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>