Skip to content

refactor(db): Postgres on effect-orm, queries and migrations - #1353

Merged
Makisuo merged 14 commits into
mainfrom
t3/try-effect-orm-postgres
Oct 9, 2026
Merged

Makisuo merged 14 commits into
mainfrom
t3/try-effect-orm-postgres

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Postgres moves from drizzle-orm to @maple-dev/effect-orm 0.5.0, for queries and for migrations. Every query is typed by its table, including the ones that used to be inline raw SQL.

Tables and queries

  • 71 tables in packages/db/src/tables (@maple/db/tables), with branded ids, literal-union enums, schema-typed jsonb and epoch-ms timestamps (PG.timestamptzMillis).
  • Services call db.run(PG.from(...)) and db.transaction(effect) through Database.execute, which still opens one span per call and records the statement.
  • Compared values are bound as $n, not written into the SQL. db.query.text no longer carries key hashes or emails.
  • The few queries the builder can't express are PG.sql templates with typed results: advisory locks, UPDATE ... FROM (VALUES), jsonb operators.
  • Numeric timestamps are compared with === null, never by truthiness, so epoch 0 isn't read as "not set". The three places that did this are fixed.
  • Readers that must accept older jsonb shapes select the column with PG.undecoded and decode it themselves, so a bad stored document can't turn into a 503.

Migrations

  • The Drizzle schema, drizzle-kit and drizzle-orm are gone. The baseline 20261009195715_effect_orm_baseline is drizzle-kit's last snapshot, and generating against it produces nothing, so the tables match the existing migrations exactly.
  • effect-orm.config.ts uses emit: "sql", so new migrations are <ts>_<name>/migration.sql in the same folder. The deploy applies the folder exactly as before.
  • db:generate, db:check, db:status and db:verify run the effect-orm kit.
  • db:migrate runs the effect-orm runner. On a database drizzle-kit migrated, the first run records drizzle's applied migrations as done, then applies only the newer ones.
  • Tests and the PGlite snapshot use the same runner.
  • 20261009202241_drop_schema_leftovers drops four objects that no table definition has and nothing uses. All four use IF EXISTS.
    • ai_triage_runs and ai_triage_settings.fanout_enabled: left behind by schema changes that never dropped them.
    • ai_triage_settings.investigation_mode and alert_destinations_org_id_replident_idx: exist only in prd. Every investigation_mode value is the default. The index isn't the replica-identity index; the table stays REPLICA IDENTITY FULL.

Verification

  • Parity: parity.test.ts checks the tables against the newest snapshot, and compares a database built by the migrations with one built from the definitions.
  • Test suites: backend 2139, api 426, ai 1123, query-engine 1664, domain 880 and db 54 pass. Typecheck and Effect lint are clean.
  • Real Postgres: the PgConnectionScope integration test passes 9/9.
  • prd data, read-only: all 71 tables (about 726k rows) decode through the new definitions (scripts/check-row-decoding.ts).
  • prd schema, read-only: the schema matches the migrations, apart from the four leftovers dropped above (scripts/check-schema-drift.ts).
  • Local app: I ran api, ai, web and electric-sync against a dev database that db:migrate had just adopted.
    • Every v2 list and detail endpoint answers.
    • Create, update, read and delete work for API keys, ingest keys, dashboards (versions, restore, shares), alert destinations and rules, attribute mappings, scrape targets, anomaly settings, mobile devices and agent feedback.
    • 25 pages make 100 API calls with no database errors.
    • The MCP OAuth flow works: register, authorize, code exchange, refresh rotation, and reuse detection revoking the token family.
    • CLI device login works, and the issued key authenticates.
    • Electric shapes sync.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Makisuo added 12 commits October 9, 2026 23:51
All 71 application tables as PG.table definitions in @maple/db/tables,
checked against drizzle-kit's head snapshot and against the catalog of a
database the bundled migrations build. Database.execute hands services
db.orm, an effect-orm database over the same client, whose statements
join an open drizzle transaction and land on the call's span.
Services run their Postgres statements through db.orm with the typed
@maple/db/tables definitions: rows decode through the table codecs,
timestamps are epoch milliseconds end to end, ids are branded and every
value is bound. Drizzle remains only under the platform layer.
…ad with PG.undecoded

Service code reads stored jsonb documents it decodes itself through
PG.undecoded instead of hand-written templates, and CASE/greatest/least/
boolean templates use the builder. Tests seed and read through db.orm;
the typed inserts rejected fixtures drizzle had accepted (an investigation
subject type that does not exist), which are fixed.
…ices effect-orm

MapleDb is now effect-orm's database: services call db.run(...) and
db.transaction(effect), statements reach the call's span through
effect-orm's observe hook, and DatabaseError absorbs effect-orm's errors.
drizzle-orm leaves backend and api; packages/db keeps drizzle-kit and its
schema only to generate and apply migrations. Platform and integration
tests run on effect-orm, the latter verified against a real Postgres.
…is gone

The baseline is drizzle-kit's last snapshot, and the tables generate
nothing against it. Migrations stay migration.sql in the same folder, so
the deploy applies them unchanged. Tests and db:migrate run the
effect-orm runner; db:migrate adopts a drizzle-migrated database once.
check-schema-drift.ts compares a live schema with the migrations.
ai_triage_runs and ai_triage_settings.fanout_enabled left the schema
without a migration dropping them. ai_triage_settings.investigation_mode
(every row the default) and alert_destinations_org_id_replident_idx (not
the replica identity; the table is FULL) exist only in prd, created
outside the migrations. The parity test no longer allows any drift.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 235 files, which is 85 over the limit of 150.

To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 74c0655b-8562-4559-93b2-a0706c21f53f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f459c2 and d2aba3b.


⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock

📒 Files selected for processing (235)
  • .env.example
  • .github/workflows/ingest-rust-tests.yml
  • .oxfmtrc.jsonc
  • CLAUDE.md
  • apps/api/package.json
  • apps/api/src/routes/internal/integrations.http.ts
  • apps/api/src/routes/scraper-internal.http.ts
  • apps/api/src/routes/scraper-internal.router.test.ts
  • apps/api/src/routes/v2/integrations.http.ts
  • apps/api/src/routes/webhooks/planetscale.http.test.ts
  • apps/api/src/routes/webhooks/planetscale.http.ts
  • apps/api/src/workflows/ClickHouseSchemaApplyWorkflow.run.ts
  • apps/api/test/integration/pg-connection-scope.integration.test.ts
  • docs/electric-sync.md
  • docs/persistence.md
  • docs/pr-review-agent-plan.md
  • docs/self-hosted-clickhouse.md
  • knip.json
  • mise.toml
  • packages/backend/package.json
  • packages/backend/src/platform/DatabaseLive.test.ts
  • packages/backend/src/platform/DatabaseLive.ts
  • packages/backend/src/platform/DatabaseOrm.test.ts
  • packages/backend/src/platform/DatabasePgLive.test.ts
  • packages/backend/src/platform/DatabasePgliteLive.ts
  • packages/backend/src/platform/distinct-org-ids.test.ts
  • packages/backend/src/platform/distinct-org-ids.ts
  • packages/backend/src/platform/electric-txid.ts
  • packages/backend/src/platform/pg-connection-scope.test.ts
  • packages/backend/src/platform/postgres-errors.test.ts
  • packages/backend/src/platform/postgres-errors.ts
  • packages/backend/src/platform/raw-rows.ts
  • packages/backend/src/platform/span-name.ts
  • packages/backend/src/platform/time.ts
  • packages/backend/src/services/alerts/AlertDeliveryDispatch.providers.test.ts
  • packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts
  • packages/backend/src/services/alerts/AlertDestinationDelivery.ts
  • packages/backend/src/services/alerts/AlertDestinationHydration.test.ts
  • packages/backend/src/services/alerts/AlertDestinationHydration.ts
  • packages/backend/src/services/alerts/AlertDestinationsService.ts
  • packages/backend/src/services/alerts/AlertReadModelsService.ts
  • packages/backend/src/services/alerts/AlertRuleModel.ts
  • packages/backend/src/services/alerts/AlertRulesService.ts
  • packages/backend/src/services/alerts/AlertsService.ts
  • packages/backend/src/services/alerts/AnomalyDetectionService.ts
  • packages/backend/src/services/alerts/EscalationService.test.ts
  • packages/backend/src/services/alerts/EscalationService.ts
  • packages/backend/src/services/alerts/NotificationDispatcher.ts
  • packages/backend/src/services/alerts/anomaly/detector-state-batch.ts
  • packages/backend/src/services/alerts/delivery/context.ts
  • packages/backend/src/services/alerts/delivery/delivery-spans.test.ts
  • packages/backend/src/services/alerts/delivery/transports/chat.test.ts
  • packages/backend/src/services/alerts/delivery/transports/render.test.ts
  • packages/backend/src/services/alerts/delivery/transports/telegram.test.ts
  • packages/backend/src/services/auth/CliDeviceAuthService.ts
  • packages/backend/src/services/auth/CloudflareOAuthService.ts
  • packages/backend/src/services/auth/HazelOAuthService.ts
  • packages/backend/src/services/auth/McpOAuthService.ts
  • packages/backend/src/services/auth/MembershipRevocationService.test.ts
  • packages/backend/src/services/auth/MembershipRevocationService.ts
  • packages/backend/src/services/auth/OAuthStateRepository.ts
  • packages/backend/src/services/auth/PlanetScaleOAuthService.ts
  • packages/backend/src/services/auth/mcp-oauth-family.ts
  • packages/backend/src/services/auth/oauth/connection-helpers.test.ts
  • packages/backend/src/services/auth/oauth/connection-helpers.ts
  • packages/backend/src/services/cancellation-review/CancellationReviewService.ts
  • packages/backend/src/services/dashboards/DashboardPersistenceService.ts
  • packages/backend/src/services/dashboards/ServiceMapRollupService.ts
  • packages/backend/src/services/dashboards/SharedDashboardService.ts
  • packages/backend/src/services/digest/DigestService.test.ts
  • packages/backend/src/services/digest/DigestService.ts
  • packages/backend/src/services/digest/WebAnalyticsDigestService.ts
  • packages/backend/src/services/errors/AiTriageService.test.ts
  • packages/backend/src/services/errors/AiTriageService.ts
  • packages/backend/src/services/errors/ErrorActorsService.ts
  • packages/backend/src/services/errors/ErrorIssueReadModelsService.test.ts
  • packages/backend/src/services/errors/ErrorIssueReadModelsService.ts
  • packages/backend/src/services/errors/ErrorIssueWorkflowService.test.ts
  • packages/backend/src/services/errors/ErrorIssueWorkflowService.ts
  • packages/backend/src/services/errors/ErrorPolicyService.test.ts
  • packages/backend/src/services/errors/ErrorPolicyService.ts
  • packages/backend/src/services/errors/ErrorsService.test.ts
  • packages/backend/src/services/errors/ErrorsService.ts
  • packages/backend/src/services/errors/FixVerificationTickService.ts
  • packages/backend/src/services/errors/InvestigationService.test.ts
  • packages/backend/src/services/errors/InvestigationService.ts
  • packages/backend/src/services/errors/IssueFixVerificationService.test.ts
  • packages/backend/src/services/errors/IssueFixVerificationService.ts
  • packages/backend/src/services/errors/RecommendationIssueService.ts
  • packages/backend/src/services/errors/ai-triage-enqueue.test.ts
  • packages/backend/src/services/errors/ai-triage-enqueue.ts
  • packages/backend/src/services/errors/apply-diagnosis.ts
  • packages/backend/src/services/errors/error-tick-persistence.ts
  • packages/backend/src/services/errors/error-tick-regression.test.ts
  • packages/backend/src/services/errors/fix-verification-enqueue.test.ts
  • packages/backend/src/services/errors/fix-verification-enqueue.ts
  • packages/backend/src/services/errors/investigation-quota.ts
  • packages/backend/src/services/errors/investigation-stale.test.ts
  • packages/backend/src/services/errors/investigation-stale.ts
  • packages/backend/src/services/errors/investigation-start.ts
  • packages/backend/src/services/errors/issue-hub.test.ts
  • packages/backend/src/services/errors/issue-hub.ts
  • packages/backend/src/services/errors/issue-severity.test.ts
  • packages/backend/src/services/errors/issue-severity.ts
  • packages/backend/src/services/feedback/AgentFeedbackService.ts
  • packages/backend/src/services/integrations/ChatWorkspaceService.ts
  • packages/backend/src/services/integrations/CloudflareAnalyticsService.test.ts
  • packages/backend/src/services/integrations/CloudflareAnalyticsService.ts
  • packages/backend/src/services/integrations/PlanetScaleConnectionService.ts
  • packages/backend/src/services/integrations/PlanetScaleDiscoveryService.ts
  • packages/backend/src/services/integrations/PlanetScaleService.ts
  • packages/backend/src/services/integrations/RailwayMetricsService.test.ts
  • packages/backend/src/services/integrations/RailwayMetricsService.ts
  • packages/backend/src/services/integrations/ScrapeTargetsService.test.ts
  • packages/backend/src/services/integrations/ScrapeTargetsService.ts
  • packages/backend/src/services/integrations/chat-identity-rows.ts
  • packages/backend/src/services/integrations/chat-outbound.ts
  • packages/backend/src/services/integrations/chat-workspace-rows.ts
  • packages/backend/src/services/integrations/planetscale-event-retention.ts
  • packages/backend/src/services/integrations/planetscale/webhook-events.test.ts
  • packages/backend/src/services/integrations/planetscale/webhook-events.ts
  • packages/backend/src/services/integrations/scrape-check-retention.ts
  • packages/backend/src/services/integrations/vcs/VcsRepository.ts
  • packages/backend/src/services/integrations/vcs/vendor/github/GithubConnectService.ts
  • packages/backend/src/services/org/ApiKeysService.ts
  • packages/backend/src/services/org/IngestAttributeMappingService.ts
  • packages/backend/src/services/org/OnboardingChecklistService.ts
  • packages/backend/src/services/org/OnboardingService.ts
  • packages/backend/src/services/org/OrgClickHouseSettingsService.ts
  • packages/backend/src/services/org/OrgIngestKeysService.ts
  • packages/backend/src/services/org/OrganizationService.org-scoped-tables.test.ts
  • packages/backend/src/services/org/OrganizationService.ts
  • packages/backend/src/services/org/SetupAuditService.ts
  • packages/backend/src/services/pr-review/PrReviewAnalyticsService.test.ts
  • packages/backend/src/services/pr-review/PrReviewAnalyticsService.ts
  • packages/backend/src/services/pr-review/PrReviewConversationService.ts
  • packages/backend/src/services/pr-review/PrReviewPostMergeService.test.ts
  • packages/backend/src/services/pr-review/PrReviewPostMergeService.ts
  • packages/backend/src/services/pr-review/PrReviewService.test.ts
  • packages/backend/src/services/pr-review/PrReviewService.ts
  • packages/backend/src/services/pr-review/telemetry/PrReviewTelemetryService.ts
  • packages/backend/src/services/push/LiveActivitiesService.ts
  • packages/backend/src/services/push/MobileDevicesService.ts
  • packages/backend/src/services/support/SupportChannelService.ts
  • packages/backend/test/pglite-snapshot.ts
  • packages/chat-platform/src/vendor-isolation.test.ts
  • packages/db/drizzle.config.ts
  • packages/db/drizzle/20261009195715_effect_orm_baseline/migration.sql
  • packages/db/drizzle/20261009195715_effect_orm_baseline/snapshot.json
  • packages/db/drizzle/20261009202241_drop_schema_leftovers/migration.sql
  • packages/db/drizzle/20261009202241_drop_schema_leftovers/snapshot.json
  • packages/db/effect-orm.config.ts
  • packages/db/package.json
  • packages/db/scripts/check-row-decoding.ts
  • packages/db/scripts/check-schema-drift.ts
  • packages/db/scripts/migrate.ts
  • packages/db/src/client.ts
  • packages/db/src/index.ts
  • packages/db/src/migrate.ts
  • packages/db/src/migrations.test.ts
  • packages/db/src/pglite.ts
  • packages/db/src/schema/agent-feedback.ts
  • packages/db/src/schema/ai-triage.ts
  • packages/db/src/schema/alerts.ts
  • packages/db/src/schema/anomalies.ts
  • packages/db/src/schema/api-keys.ts
  • packages/db/src/schema/cancellation-reviews.ts
  • packages/db/src/schema/cli-device-authorizations.ts
  • packages/db/src/schema/cloudflare-analytics-state.ts
  • packages/db/src/schema/cloudflare-hyperdrive-configs.ts
  • packages/db/src/schema/cloudflare-logpush-connectors.ts
  • packages/db/src/schema/dashboards.ts
  • packages/db/src/schema/digest.ts
  • packages/db/src/schema/errors.ts
  • packages/db/src/schema/escalations.ts
  • packages/db/src/schema/index.ts
  • packages/db/src/schema/investigations.ts
  • packages/db/src/schema/live-activities.ts
  • packages/db/src/schema/mcp-oauth.ts
  • packages/db/src/schema/mobile-devices.ts
  • packages/db/src/schema/oauth-connections.ts
  • packages/db/src/schema/onboarding.ts
  • packages/db/src/schema/org-clickhouse-schema-apply-runs.ts
  • packages/db/src/schema/org-clickhouse-settings.ts
  • packages/db/src/schema/org-ingest-attribute-mappings.ts
  • packages/db/src/schema/org-ingest-keys.ts
  • packages/db/src/schema/org-ingest-sampling-policies.ts
  • packages/db/src/schema/org-recommendation-issues.ts
  • packages/db/src/schema/planetscale-connections.ts
  • packages/db/src/schema/planetscale-inventory.ts
  • packages/db/src/schema/railway.ts
  • packages/db/src/schema/scrape-targets.ts
  • packages/db/src/schema/support-channels.ts
  • packages/db/src/schema/vcs.ts
  • packages/db/src/tables/agent-feedback.ts
  • packages/db/src/tables/ai-triage.ts
  • packages/db/src/tables/alerts.ts
  • packages/db/src/tables/anomalies.ts
  • packages/db/src/tables/api-keys.ts
  • packages/db/src/tables/cancellation-reviews.ts
  • packages/db/src/tables/chat-identities.ts
  • packages/db/src/tables/chat-workspaces.ts
  • packages/db/src/tables/cli-device-authorizations.ts
  • packages/db/src/tables/cloudflare-analytics-state.ts
  • packages/db/src/tables/cloudflare-hyperdrive-configs.ts
  • packages/db/src/tables/cloudflare-logpush-connectors.ts
  • packages/db/src/tables/dashboard-shares.ts
  • packages/db/src/tables/dashboards.ts
  • packages/db/src/tables/digest.ts
  • packages/db/src/tables/errors.ts
  • packages/db/src/tables/escalations.ts
  • packages/db/src/tables/index.ts
  • packages/db/src/tables/investigations.ts
  • packages/db/src/tables/live-activities.ts
  • packages/db/src/tables/mcp-oauth.ts
  • packages/db/src/tables/mobile-devices.ts
  • packages/db/src/tables/oauth-connections.ts
  • packages/db/src/tables/onboarding.ts
  • packages/db/src/tables/org-clickhouse-schema-apply-runs.ts
  • packages/db/src/tables/org-clickhouse-settings.ts
  • packages/db/src/tables/org-ingest-attribute-mappings.ts
  • packages/db/src/tables/org-ingest-keys.ts
  • packages/db/src/tables/org-ingest-sampling-policies.ts
  • packages/db/src/tables/org-recommendation-issues.ts
  • packages/db/src/tables/parity-harness.ts
  • packages/db/src/tables/parity.test.ts
  • packages/db/src/tables/planetscale-connections.ts
  • packages/db/src/tables/planetscale-inventory.ts
  • packages/db/src/tables/railway.ts
  • packages/db/src/tables/scrape-targets.ts
  • packages/db/src/tables/support-channels.ts
  • packages/db/src/tables/vcs.ts
  • packages/domain/package.json
  • packages/query-engine-integrations/package.json
  • packages/query-engine/package.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Note

A newer push replaced b293202 before its review finished. The latest commit is reviewed in a new comment.

@Makisuo Makisuo added the preview Deploy a full PR preview stack (Cloudflare + AWS ingest); removing it tears it down label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit d2aba3b · View workflow run

@maple-review-bot

maple-review-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 5/10 · needs attention
One confirmed strict-jsonb decode regression, and two delegated review groups plus ~106 files were never read.
quality 90/100 · 1 warning · tests covered · risk high · 1/1 new units observable · 136 files not read

Moves every Postgres query and the migration tooling from drizzle-orm to @maple-dev/effect-orm, with typed table definitions, a parity harness and a new baseline migration. The mechanical port is faithful where I read it, but it introduces one strict-jsonb decode regression in the post-merge tick. I read the platform/db core, the PR-review and chat-integration services by hand; the other 200+ files were delegated to child reviewers (alerts, errors, auth, integrations, org, pr-review returned no findings; two groups — errors part 2, and dashboards/digest/push/support/feedback — did not finish), so those areas are not vouched for by me.

  • Database.execute runs effect-orm over the same @effect/sql-pg client, one span per call
  • Table definitions in @maple/db/tables with PG.timestamptzMillis and schema-typed jsonb
  • Migrations and the ledger move to effect-orm migrate; drizzle-kit and drizzle-orm removed
  • 20261009202241_drop_schema_leftovers drops four prd-only objects with IF EXISTS

Before merge

Findings

🟠 Warning · F1 · telemetryJson now decodes strictly, so an old stored document fails the whole tick

correctness · packages/backend/src/services/pr-review/PrReviewPostMergeService.ts:101

PrReviews.telemetryJson is declared PG.jsonb(PrReviewTelemetry) (packages/db/src/tables/vcs.ts:235), so every due-row selected at PrReviewPostMergeService.ts:277 must decode against the current PrReviewTelemetry shape. The removed decodeTelemetry = Schema.decodeUnknownOption(PrReviewTelemetry) tolerated an older document, and the strict decode now fails the whole statement, so one row written before a shape change leaves every due post-merge row in that tick unsettled — the no_traffic/give_up path at line 108 is never reached and only the persistence failure is logged. Select the column as telemetryJson: PG.undecoded($.telemetryJson) and decode it leniently, as PrReviewService.getReview does.

Select `telemetryJson: PG.undecoded($.telemetryJson)` in the `due` query and keep a `Schema.decodeUnknownOption(PrReviewTelemetry)` for `facts`.
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit 6260c078fd59c41c4033e09ed04eb507be0d3a89. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · correctness · packages/backend/src/services/pr-review/PrReviewPostMergeService.ts:101
`telemetryJson` now decodes strictly, so an old stored document fails the whole tick
`PrReviews.telemetryJson` is declared `PG.jsonb(PrReviewTelemetry)` (`packages/db/src/tables/vcs.ts:235`), so every due-row selected at `PrReviewPostMergeService.ts:277` must decode against the current `PrReviewTelemetry` shape. The removed `decodeTelemetry = Schema.decodeUnknownOption(PrReviewTelemetry)` tolerated an older document, and the strict decode now fails the whole statement, so one row written before a shape change leaves every due post-merge row in that tick unsettled — the `no_traffic`/`give_up` path at line 108 is never reached and only the persistence failure is logged. Select the column as `telemetryJson: PG.undecoded($.telemetryJson)` and decode it leniently, as `PrReviewService.getReview` does.
Suggested fix: Select `telemetryJson: PG.undecoded($.telemetryJson)` in the `due` query and keep a `Schema.decodeUnknownOption(PrReviewTelemetry)` for `facts`.

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/http/errors/WarehouseAuthError maple-ai 20 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-ai 16 packages/backend/src/platform/DatabaseLive.ts
@maple/api/errors/McpOAuthProtocolError maple-api 15 packages/backend/src/services/org/OnboardingChecklistService.ts
TypeError maple-api 10 packages/backend/src/services/org/OnboardingChecklistService.ts
@maple/http/errors/WarehouseQueryError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-ai 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseAuthError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseUpstreamError maple-cli 8 packages/db/src/schema/errors.ts
@maple/http/errors/WarehouseQueryError maple-ai 8 packages/backend/src/platform/DatabaseLive.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
.env.example 325k maple 325k/day, 99.3% err
packages/backend/src/services/alerts/AlertsService.ts 289k AlertsService.processEvaluation 284k/day, 0.0% err
AlertsService.processQueuedDeliveries 2.8k/day, 0.0% err
AlertsService.runSchedulerTick 2.8k/day, 0.0% err
packages/backend/src/services/alerts/AlertRuleModel.ts 173k AlertsService.normalizeRuleRow 173k/day, 0.0% err
AlertsService.normalizeRuleRow 2/day, 0.0% err
AlertsService.normalizeRuleRow 1/day, 0.0% err
packages/backend/src/services/errors/ErrorsService.ts 42k ErrorsService.processOrg 39k/day, 0.3% err
ErrorsService.processNotificationOutbox 2.8k/day, 0.0% err
packages/db/src/tables/scrape-targets.ts 8.2k POST /api/internal/scrape-results 8.2k/day, 0.0% err
packages/backend/src/services/alerts/AnomalyDetectionService.ts 7.0k AnomalyDetectionService.loadSettingsRow 7.0k/day, 0.0% err
AnomalyDetectionService.listIncidents 11/day, 0.0% err
packages/backend/src/services/errors/IssueFixVerificationService.ts 5.6k IssueFixVerification.settledRuns 2.8k/day, 0.0% err
IssueFixVerification.dueVerifications 2.8k/day, 0.0% err
IssueFixVerification.hydrateLinks 1/day, 0.0% err
packages/backend/src/services/auth/oauth/connection-helpers.ts 5.0k OAuthConnectionHelpers.requireConnection 5.0k/day, 0.0% err
packages/backend/src/platform/span-name.ts 4.4k SELECT alert_rules 2.9k/day, 0.0% err
Database.execute 1.2k/day, 0.0% err
SELECT alert_rules 238/day, 0.0% err
packages/backend/src/services/alerts/EscalationService.ts 2.8k EscalationService.runEscalationTick 2.8k/day, 0.0% err
EscalationService.processOne 11/day, 0.0% err
packages/db/src/client.ts 1.2k Database.execute 1.2k/day, 0.0% err
packages/backend/src/services/integrations/RailwayMetricsService.ts 1.2k RailwayMetricsService.emitMetrics 647/day, 0.0% err
RailwayMetricsService.pollAllOrgs 504/day, 0.0% err
packages/backend/src/services/integrations/CloudflareAnalyticsService.ts 579 CloudflareAnalyticsService.pollAllOrgs 579/day, 0.0% err
packages/backend/src/services/integrations/PlanetScaleService.ts 579 PlanetScaleService.pollAllOrgs 579/day, 0.0% err
packages/backend/src/services/integrations/vcs/VcsRepository.ts 370 VcsRepository.getPrReviewSettings 191/day, 0.0% err
VcsRepository.getPrReviewSettings 174/day, 0.0% err
VcsRepository.listAllInstallations 5/day, 0.0% err
What was checked
  • selectDistinctOrgIds keeps the loose index scan and now takes the table, not a column (distinct-org-ids.ts:31)
  • toDatabaseError and driverSqlError still unwrap the driver SqlError for SQLSTATE classification (DatabaseLive.ts:81)
  • Span capture survives the logger swap: DatabaseOrm.test.ts:63 asserts SELECT api_keys and db.query.text with $1, never the value
Observability coverage: 1 of 1 changes observable
Change Kind Observable Evidence
SQL statement per Database.execute call db yes makeMapleDb observe -> MapleStatementCollector; DatabaseOrm.test.ts asserts span name and db.query.text
Files not reviewed (136)

The review ended before it read these diffs, so nothing above vouches for them.

  • .env.example
  • .github/workflows/ingest-rust-tests.yml
  • .oxfmtrc.jsonc
  • apps/api/package.json
  • apps/api/src/routes/scraper-internal.router.test.ts
  • apps/api/src/routes/webhooks/planetscale.http.test.ts
  • apps/api/test/integration/pg-connection-scope.integration.test.ts
  • knip.json
  • packages/backend/package.json
  • packages/backend/src/platform/DatabaseLive.test.ts
  • packages/backend/src/platform/DatabasePgLive.test.ts
  • packages/backend/src/platform/DatabasePgliteLive.ts
  • packages/backend/src/platform/distinct-org-ids.test.ts
  • packages/backend/src/platform/pg-connection-scope.test.ts
  • packages/backend/src/platform/postgres-errors.test.ts
  • packages/backend/src/platform/raw-rows.ts
  • packages/backend/src/platform/span-name.ts
  • packages/backend/src/services/alerts/AlertDeliveryDispatch.providers.test.ts
  • packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts
  • packages/backend/src/services/alerts/AlertDestinationHydration.test.ts
  • packages/backend/src/services/alerts/EscalationService.test.ts
  • packages/backend/src/services/alerts/anomaly/detector-state-batch.ts
  • packages/backend/src/services/alerts/delivery/context.ts
  • packages/backend/src/services/alerts/delivery/delivery-spans.test.ts
  • packages/backend/src/services/alerts/delivery/transports/chat.test.ts
  • packages/backend/src/services/alerts/delivery/transports/render.test.ts
  • packages/backend/src/services/alerts/delivery/transports/telegram.test.ts
  • packages/backend/src/services/auth/MembershipRevocationService.test.ts
  • packages/backend/src/services/auth/oauth/connection-helpers.test.ts
  • packages/backend/src/services/digest/DigestService.test.ts
  • and 106 more

6260c07 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo
Makisuo deployed to pr-preview October 9, 2026 22:00 — with GitHub Actions Active

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

Comment thread packages/backend/src/services/pr-review/PrReviewPostMergeService.ts Outdated
…y again

Both reads tolerated a stored document in another shape before the
migration: a strict column decode failed the whole tick or the whole
target list instead of the one row.
@maple-review-bot

maple-review-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 9/10 · safe to merge
Only the changed-since files were read; the F1 lenient-decode path is pinned by a test that stubs a malformed document.
quality 100/100 · no findings · tests covered · risk medium

The last commit's hunks read stored jsonb leniently on the two paths that touch it — pr-review telemetry and scrape-target labels — and carry epoch-ms timestamps with null checks. The open warehouse and OAuth error issues are in other files and untouched; safe to merge.

  • PrReviewPostMergeService selects due telemetry with PG.undecoded, decodes it with decodeUnknownOption
  • ScrapeTargetsService.listAllEnabled returns labels as stored, typed unknown
  • Scrape-target and pr-review timestamps are epoch millis, tested with === null

Before merge

Fixed since the last review

  • ✅ F1 · telemetryJson now decodes strictly, so an old stored document fails the whole tick

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/http/errors/WarehouseAuthError maple-ai 20 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-ai 16 packages/backend/src/platform/DatabaseLive.ts
@maple/api/errors/McpOAuthProtocolError maple-api 15 packages/backend/src/services/org/OnboardingChecklistService.ts
TypeError maple-api 10 packages/backend/src/services/org/OnboardingChecklistService.ts
@maple/http/errors/WarehouseQueryError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-ai 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseQueryError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseAuthError maple-chat 8 packages/backend/src/platform/DatabaseLive.ts
@maple/http/errors/WarehouseUpstreamError maple-cli 8 packages/db/src/schema/errors.ts
@maple/http/errors/WarehouseQueryError maple-ai 8 packages/backend/src/platform/DatabaseLive.ts

After this merges, Maple checks whether they stop.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
.env.example 323k maple 323k/day, 99.3% err
packages/backend/src/services/alerts/AlertsService.ts 288k AlertsService.processEvaluation 282k/day, 0.0% err
AlertsService.processQueuedDeliveries 2.8k/day, 0.0% err
AlertsService.runSchedulerTick 2.8k/day, 0.0% err
packages/backend/src/services/alerts/AlertRuleModel.ts 172k AlertsService.normalizeRuleRow 172k/day, 0.0% err
AlertsService.normalizeRuleRow 2/day, 0.0% err
AlertsService.normalizeRuleRow 1/day, 0.0% err
packages/backend/src/services/errors/ErrorsService.ts 41k ErrorsService.processOrg 39k/day, 0.3% err
ErrorsService.processNotificationOutbox 2.8k/day, 0.0% err
packages/db/src/tables/scrape-targets.ts 8.1k POST /api/internal/scrape-results 8.1k/day, 0.0% err
packages/backend/src/services/alerts/AnomalyDetectionService.ts 7.0k AnomalyDetectionService.loadSettingsRow 7.0k/day, 0.0% err
AnomalyDetectionService.listIncidents 11/day, 0.0% err
packages/backend/src/services/integrations/ScrapeTargetsService.ts 5.6k ScrapeTargetsService.authHeadersForRow 5.6k/day, 0.0% err
packages/backend/src/services/errors/IssueFixVerificationService.ts 5.5k IssueFixVerification.settledRuns 2.8k/day, 0.0% err
IssueFixVerification.dueVerifications 2.8k/day, 0.0% err
IssueFixVerification.hydrateLinks 2/day, 0.0% err
packages/backend/src/services/auth/oauth/connection-helpers.ts 5.0k OAuthConnectionHelpers.requireConnection 5.0k/day, 0.0% err
packages/backend/src/platform/span-name.ts 4.3k SELECT alert_rules 2.8k/day, 0.0% err
Database.execute 1.2k/day, 0.0% err
SELECT alert_rules 238/day, 0.0% err
packages/backend/src/services/alerts/EscalationService.ts 2.8k EscalationService.runEscalationTick 2.8k/day, 0.0% err
EscalationService.processOne 11/day, 0.0% err
packages/db/src/client.ts 1.2k Database.execute 1.2k/day, 0.0% err
packages/backend/src/services/integrations/RailwayMetricsService.ts 1.2k RailwayMetricsService.emitMetrics 650/day, 0.0% err
RailwayMetricsService.pollAllOrgs 505/day, 0.0% err
packages/backend/src/services/integrations/CloudflareAnalyticsService.ts 577 CloudflareAnalyticsService.pollAllOrgs 577/day, 0.0% err
packages/backend/src/services/integrations/PlanetScaleService.ts 577 PlanetScaleService.pollAllOrgs 577/day, 0.0% err
What was checked
  • F1 fixes: due selects telemetryJson with PG.undecoded (PrReviewPostMergeService.ts:285); the malformed-document test settles no_traffic
  • listAllEnabled labels stay raw: test asserts { team: 7 } survives (ScrapeTargetsService.test.ts:120)
  • Every lastScrapeAt/mergedAt guard is !== null, never truthiness (ScrapeTargetsService.ts:408, PrReviewPostMergeService.ts:106)

d2aba3b · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo

Makisuo commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

On the before-merge checklist:

  • Ledger adoption on prd: nothing needs adopting. prd is migrated by the deploy (Planetscale.PostgresBranch in alchemy.run.ts), which keeps its own __alchemy_migrations ledger and applies every <ts>_<name>/migration.sql it hasn't recorded. That doesn't change here: the folder and its layout stay the same. effect-orm's ledger is only used locally (db:migrate, tests), and the drizzle-ledger adoption in scripts/migrate.ts only applies to local databases.
  • The two migrations: the baseline is a single SQL comment, so the deploy records it and runs nothing. The drop migration uses IF EXISTS throughout. Nothing in the old or new code reads the four dropped objects; I checked prd read-only before writing it. alert_destinations stays REPLICA IDENTITY FULL, and the dropped index was not its replica-identity index.

@Makisuo
Makisuo deployed to pr-preview October 9, 2026 22:14 — with GitHub Actions Active
@Makisuo
Makisuo merged commit 5a86c1b into main Oct 9, 2026
46 checks passed
@Makisuo
Makisuo deleted the t3/try-effect-orm-postgres branch October 9, 2026 22:22
@Makisuo
Makisuo deployed to pr-preview October 9, 2026 22:22 — with GitHub Actions Active
Makisuo added a commit that referenced this pull request Oct 9, 2026
…2 test (#1357)

#1353 moved Postgres to effect-orm, where timestamptzMillis columns decode to
numbers. The 402 plan-blocked test from #1354 still seeded and asserted Dates.

This branch was successfully deployed

1 active deployment
pr-preview — d2aba3b6 Deployed Oct 9, 2026 by Makisuo via deploy-pr-preview #4354
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Deploy a full PR preview stack (Cloudflare + AWS ingest); removing it tears it down

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant