Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commitb45db84unknown_key

feat(db): add processed_events idempotency table

feat(db): add processed_events idempotency table

Signal Bus P1 — introduces a `processed_events` table used by the new
inbound events receiver to short-circuit duplicate deliveries from
Crontech (retry, at-least-once bus, manual replay). Records (eventId,
eventType, source, receivedAt, payload) with a UNIQUE constraint on
eventId and a supporting index.

Drizzle schema lives in a NEW module `src/db/schema-events.ts` because
`src/db/schema.ts` is in BUILD_BIBLE.md §4 LOCKED BLOCKS — the repo's
documented pattern is "new tables only via new migration", so the
Drizzle schema for this new migration is added alongside rather than
inside the locked aggregate schema file.
Claude committed on April 15, 2026Parent: 43cf9b0
2 files changed+620b45db843a4a5ca264c9ac15215a67e9be724b3de
2 changed files+62−0
Addeddrizzle/0034_processed_events.sql+30−0View fileUnifiedSplit
1-- Signal Bus P1 — Idempotency table for inbound events.
2--
3-- Records every external event we have successfully processed so that
4-- duplicate deliveries (Crontech retry, network replay, at-least-once bus)
5-- can be detected and short-circuited without firing side-effects twice.
6--
7-- `event_id` is the provider-supplied uuid v4 carried on the wire.
8-- `source` namespaces event_id in case two providers happen to collide
9-- (e.g. 'crontech', 'gatetest', 'github').
10--
11-- This migration is additive and reversible — drop the table to remove it.
12
13CREATE TABLE IF NOT EXISTS "processed_events" (
14 "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
15 "event_id" text NOT NULL UNIQUE,
16 "event_type" text NOT NULL,
17 "source" text NOT NULL,
18 "received_at" timestamp NOT NULL DEFAULT now(),
19 "payload" jsonb
20);
21
22--> statement-breakpoint
23
24CREATE INDEX IF NOT EXISTS "processed_events_event_id_idx"
25 ON "processed_events" ("event_id");
26
27--> statement-breakpoint
28
29CREATE INDEX IF NOT EXISTS "processed_events_source_type_idx"
30 ON "processed_events" ("source", "event_type");
Addedsrc/db/schema-events.ts+32−0View fileUnifiedSplit
1/**
2 * Signal Bus P1 — Drizzle schema for the inbound-event idempotency table.
3 *
4 * Defined in a SEPARATE module because `src/db/schema.ts` is listed in
5 * §4 LOCKED BLOCKS of BUILD_BIBLE.md and must not be edited. New tables are
6 * allowed "only via new migration"; this module supplies the matching Drizzle
7 * definitions that migration `drizzle/0034_processed_events.sql` creates at
8 * the SQL layer. Import `processedEvents` directly from this module.
9 *
10 * Columns mirror the SQL migration 1:1. Keep them in sync whenever the
11 * migration is superseded by a follow-up additive migration.
12 */
13
14import { index, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
15
16export const processedEvents = pgTable(
17 "processed_events",
18 {
19 id: uuid("id").primaryKey().defaultRandom(),
20 eventId: text("event_id").notNull().unique(),
21 eventType: text("event_type").notNull(),
22 source: text("source").notNull(),
23 receivedAt: timestamp("received_at").defaultNow().notNull(),
24 // Raw payload retained for forensics / replay. jsonb column — callers
25 // pass a plain object and drizzle serialises it.
26 payload: jsonb("payload"),
27 },
28 (table) => [
29 index("processed_events_event_id_idx").on(table.eventId),
30 index("processed_events_source_type_idx").on(table.source, table.eventType),
31 ]
32);
033