goodkeeper

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Added

  • Public changelog + version badge: a /changelog page (public, no auth) that renders this CHANGELOG.md from a small Keep-a-Changelog parser (src/lib/changelog.ts), and a .figure-mono version badge on the landing page linking to it. The displayed version is the latest released CHANGELOG header, falling back to package.json version until a release is cut — the GoodFunds sibling's mechanism, adapted to GoodKeeper's design tokens.
  • Notifications system: per-tenant escalation and cadence selection, email and HMAC-webhook adapters, config-guard SSRF defense, fenced system-context cron sweep with Vercel scheduled-function support, and nightly digest dispatch with at-least-once semantics.
  • MCP surface (/api/mcp): 24 authenticated tools for agent access, agent-key bearer auth scoped to households, registry invariants (tool count, names, schemas), and live-smoke coverage (malformed/absent key rejection, multi-tenant failure mode sanitization).
  • Confirm-first ingestion pipeline: proposal schemas bound to domain write schemas, fuzzy vehicle resolution, and an open/extract/get/reject/confirm pipeline. Confirm is transactional (composing Plan 2 tx-cores), rollback-proof, and driven by capture and confirm_capture MCP tools. Server extractor uses AI SDK via Vercel AI Gateway (images and PDFs).
  • Foundation: Next.js (App Router) + TypeScript scaffold, Supabase (Auth, Storage, local Postgres via the Supabase CLI), and Drizzle ORM wired up end to end, with portable env loading and a vitest @ alias.
  • Multi-tenant domain layer, entirely in its own goodkeeper Postgres schema (never public), RLS-scoped throughout:
    • Tenancy tables and RLS isolation, backed by a goodkeeper_private.current_tenant_ids() helper and a shared tenantRlsPolicies() policy builder (src/db/schema/_rls.ts).
    • Asset / vehicle / odometer cluster, and the maintenance record/planned/schedule/template cluster, each with RLS.
    • Provider and attachment tables, with provider FKs onto maintenance records and planned services.
    • Notification and ingest-job tables, RLS-scoped.
    • A storage bucket (goodkeeper-attachments, private) with RLS policies, plus a seed script for global service templates.
    • Agent identities: agent service users are provisioned as scoped Supabase users (app_metadata.kind = 'agent', agents.goodkeeper.local emails), with their writes fenced separately from human-user writes.
    • Admin (RLS-bypassing, fenced to tenant creation/invites/seeds/tests) and RLS (request-scoped, createDrizzle(token).rls) Drizzle transaction clients.
  • Repo guardrails in CLAUDE.md: no drizzle-kit push, local-only tooling (never point dev config at hosted Supabase), and the drizzle migration/custom-SQL split.
  • Good-family repo standards: this CONTRIBUTING.md, this changelog, a .github/workflows/ci.yml (local Supabase stack, migrate, seed, test, typecheck, schema-drift check on PRs and main), and additive CLAUDE.md sections (git workflow, commands, issue-tracking convention).

Changed

  • App schema moved from public to its own goodkeeper schema, with its own Drizzle migration history (drizzle.__drizzle_migrations_goodkeeper), so it can coexist inside a Supabase project shared with other Good-family apps without touching their objects.
  • Repo renamed to GoodKeeper end to end (docs, supabase/config.toml project_id, scratch-directory ignores).

Fixed

  • Forbid non-admin creation or privilege-flip of global service templates.
  • Fenced agent_identity writes and hardened resolveAgentToken against a security gap in agent-token resolution.
  • Kept the migration journal's timestamps monotonic after a migration reorder, so drizzle-kit's ordering assumptions hold.
  • Let drizzle-kit own the provider-FK migration lineage rather than hand-editing around it.

Known deferred

  • Composite-FK hardening (cross-tenant FK plants): RLS scopes rows by tenant_id, but Postgres FK checks don't consult RLS, so a caller can insert/update a row whose FK points at another tenant's row as long as its own row satisfies its own tenant-scoped check. Full list of affected columns and the two ways it can be exploited (delete-cascade griefing, unique-slot pre-emption) tracked in CLAUDE.md.