Skip to content

feat(#18): orphan events migration - #35

Merged
jpricardo merged 1 commit into
feat/multi-tenancyfrom
feat/startup-migration
Aug 22, 2026
Merged

feat(#18): orphan events migration#35
jpricardo merged 1 commit into
feat/multi-tenancyfrom
feat/startup-migration

Conversation

@jpricardo

Copy link
Copy Markdown
Owner

Closes #18.

Existing deployments have logs, api_keys, and settings with no project_id. Without a migration, shipping multi-tenancy makes all of that data invisible. Logger now adopts it into a Default project before it serves traffic.

How it works

toolbox/data/migrate.go holds the DB-level work:

  • CountOrphanedDocuments counts documents whose project_id is missing, null, or an empty string
  • MigrateOrphansToDefaultProject returns a nil report and does nothing when that count is zero — this is what makes reruns a no-op. Otherwise it finds-or-creates project Default / default (duplicate-key safe), stamps each collection with UpdateMany, then upserts an owner ProjectMember per allowlisted login with $setOnInsert, so an existing membership is never downgraded
  • ParseGithubLogins trims and dedupes LOGWOLF_ALLOWED_GITHUB_USERS but preserves case — memberships are matched against the login GitHub returns at sign-in, so folding case would lock a mixed-case user out of the project created for them

logger/cmd/api/migrate.go reads the env var, logs the summary, and warns when the allowlist is empty (the project would have no owners and stay invisible in the dashboard). It is called from main.go before serve(), so the RPC port only opens on a fully migrated database. Failures are logged rather than fatal — the migration is idempotent, so the next start retries instead of crash-looping the stack.

Two additions beyond the issue text

  1. EnsureProjectIndexes() was never called in production — only from an integration test, so the unique slug index did not exist on a real deployment. The migration's find-or-create relies on it. Now called at logger startup.
  2. The legacy TTL index is dropped. An upgraded deployment still carries the pre-multi-tenancy global TTL index on logs.created_at; left in place it keeps purging on the old global schedule and overrides per-project retention (a project set to "forever" would still lose data).

docker-compose.yml now passes LOGWOLF_ALLOWED_GITHUB_USERS to logger — without it an upgrade migrates the data but leaves the Default project ownerless.

Also bundled (not part of #18)

Two unrelated fixes rode along on this branch; they are independent and can be reviewed separately:

  • CLEANUP_INTERVAL never reached the logger container. It was documented in .env.example but absent from docker-compose.yml, so setting it had no effect. Also added CI steps for the logger and listener modules — logger/cmd/api/rpc_test.go existed but had never run in CI.
  • The integration harness leaked service processes. startProcess ran services via go run and killed only that parent; on Windows the service survived. Strays accumulated across runs and a stray Listener reconnecting to a recycled RabbitMQ port ate messages a later test was waiting for, causing false failures in the TestProjectIsolation_* tests. startProcess now builds each service once and execs the binary directly, so the killed process is the service.

Verification

  • Unit tests: broker, toolbox (including new migrate_test.go), logger — all pass
  • Integration suite: 24/24, run twice back-to-back with no cleanup between runs, leaving zero stray processes
  • New integration/startup_migration_test.go seeds a pre-multi-tenancy database, boots the real logger, and asserts after the RPC port opens; a second boot with an extra allowlist entry proves idempotency; a third test reads the legacy events back through the broker's public GET /logs using the legacy API key
  • Manual boot against a seeded database, confirming ordering and log wording:
Migration: dropped the legacy global TTL index on logs — retention is per project now
Migration: adopted pre-multi-tenancy data into project "Default" project_id=6a89d492354cc53eb0a477fe logs=2 api_keys=1 settings=1 owners=2
Starting RPC server on port 35001

A second boot printed nothing.

Note for upgrades

The broker caches an API key's project for 60s, so a key validated in the seconds before the migration can read empty until that entry expires. Self-healing; documented in the logger overview.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
logwolf-docs Ready Ready Preview Aug 22, 2026 5:01pm

@jpricardo
jpricardo merged commit 9391163 into feat/multi-tenancy Aug 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants