feat(fleet): native Mosaic backlog on @mosaicstack/db (atomic claim + TTL) (#657)
This commit was merged in pull request #657.
This commit is contained in:
@@ -587,6 +587,62 @@ export const summarizationJobs = pgTable(
|
||||
(t) => [index('summarization_jobs_status_idx').on(t.status)],
|
||||
);
|
||||
|
||||
// ─── Fleet Backlog ────────────────────────────────────────────────────────────
|
||||
// Mosaic-native backlog-of-record (card A4). This REPLACES the former Hermes
|
||||
// adapter — there is NO runtime dependency on Hermes. Cards form a dependency
|
||||
// DAG (`depends_on`), are claimed atomically by fleet workers via
|
||||
// `SELECT ... FOR UPDATE SKIP LOCKED`, and auto-expire via a TTL so a crashed
|
||||
// claimer's card returns to the pool.
|
||||
|
||||
/**
|
||||
* Lifecycle status of a backlog card.
|
||||
* - ready: eligible to be claimed (once its deps are all `done`).
|
||||
* - claimed: a worker holds it (claim_owner + claimed_at set); may expire via TTL.
|
||||
* - blocked: explicitly parked; never auto-claimed.
|
||||
* - done: completed; satisfies dependents.
|
||||
*/
|
||||
export const backlogStatusEnum = pgEnum('backlog_status', ['ready', 'claimed', 'blocked', 'done']);
|
||||
|
||||
export const backlog = pgTable(
|
||||
'backlog',
|
||||
{
|
||||
/** Stable, caller-supplied card id (e.g. "A4", "fleet-001"). PK. */
|
||||
id: text('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
body: text('body'),
|
||||
/** Board/phase grouping (e.g. "M1", "fleet"). Free-form. */
|
||||
phase: text('phase'),
|
||||
/** Higher number = higher priority; claim picks the max-priority ready card. */
|
||||
priority: integer('priority').notNull().default(0),
|
||||
status: backlogStatusEnum('status').notNull().default('ready'),
|
||||
/** DAG edges: ids of cards this one depends on. "ready" requires all done. */
|
||||
dependsOn: jsonb('depends_on').notNull().$type<string[]>().default([]),
|
||||
/** Owner token of the current claim (worker/agent id). NULL when unclaimed. */
|
||||
claimOwner: text('claim_owner'),
|
||||
/** TTL of the active claim in seconds. NULL when unclaimed. */
|
||||
claimTtlSeconds: integer('claim_ttl_seconds'),
|
||||
/** When the active claim was taken. NULL when unclaimed. claimed_at + ttl = expiry. */
|
||||
claimedAt: timestamp('claimed_at', { withTimezone: true }),
|
||||
/** Count of times this card has been claimed (incremented on each claim). */
|
||||
attempts: integer('attempts').notNull().default(0),
|
||||
/** Optional dedup key for `create`; a repeat key returns the existing card. */
|
||||
idempotencyKey: text('idempotency_key'),
|
||||
/** Acceptance criteria — free-form JSON (array of strings or object). */
|
||||
acceptance: jsonb('acceptance'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [
|
||||
// Hot path: claim scans ready cards ordered by priority then age.
|
||||
index('backlog_status_priority_idx').on(t.status, t.priority),
|
||||
// reclaim sweeps claimed cards by claimed_at to find expired ones.
|
||||
index('backlog_status_claimed_at_idx').on(t.status, t.claimedAt),
|
||||
// Idempotent create dedups on this key (NULLs are distinct in Postgres, so
|
||||
// many unkeyed cards coexist; a repeated non-null key collides).
|
||||
uniqueIndex('backlog_idempotency_key_idx').on(t.idempotencyKey),
|
||||
],
|
||||
);
|
||||
|
||||
// ─── Federation ──────────────────────────────────────────────────────────────
|
||||
// Enums declared before tables that reference them.
|
||||
// All federation definitions live in this file (avoids CJS/ESM cross-import
|
||||
|
||||
Reference in New Issue
Block a user