feat(broker): record daemon idempotency fields on message_queue
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Additive plumbing for v0.9.0 daemon spec §4.2/§4.4. Adds two nullable
columns to mesh.message_queue — client_message_id (caller-supplied) and
request_fingerprint (canonical sha256 of the send shape) — and threads
them through the broker:

  - handleSend reads them off the wire envelope when present
  - queueMessage persists them on the row
  - drainForMember projects them onto the push so receiving daemons
    can dedupe their local inbox by client_message_id

Columns stay nullable so legacy traffic (launch CLI, dashboard chat)
continues to flow uninterrupted. Sprint 7 (broker hardening) will add
the partial unique index and the client_message_dedupe atomic-accept
table once we're ready to enforce dedupe broker-side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-03 20:05:36 +01:00
parent abaa4bcf87
commit bf22afb0ed
5 changed files with 62 additions and 4 deletions

View File

@@ -359,6 +359,14 @@ export const messageQueue = meshSchema.table("message_queue", {
createdAt: timestamp().defaultNow().notNull(),
deliveredAt: timestamp(),
expiresAt: timestamp(),
// v0.9.0 daemon: caller-supplied idempotency id (spec §4.2). Nullable
// for legacy traffic. Sprint 7+ promotes it to a partial-unique index
// and adds the mesh.client_message_dedupe table for atomic accept.
clientMessageId: text("client_message_id"),
// v0.9.0 daemon: 32-byte sha256 of the canonical request shape (spec
// §4.4), hex-encoded. Nullable for legacy traffic. Brokers that want
// to enforce idempotency on retries will read this column.
requestFingerprint: text("request_fingerprint"),
});
/**
@@ -1658,10 +1666,10 @@ export type InsertMeshNotification = typeof meshNotification.$inferInsert;
* ──────────────────────────────────────────────────────────────────────── */
export const apiKeyCapabilityEnum = meshSchema.enum("api_key_capability", [
"send", // POST /messages
"read", // GET /messages, /peers, /state
"send", // POST /messages
"read", // GET /messages, /peers, /state
"state_write", // POST /state
"admin", // issue/revoke other keys, delete topics, etc.
"admin", // issue/revoke other keys, delete topics, etc.
]);
export const meshApiKey = meshSchema.table(
@@ -1679,7 +1687,7 @@ export const meshApiKey = meshSchema.table(
secretPrefix: text().notNull(),
/** Granted capabilities. Empty = no permissions; key is a stub. */
capabilities: jsonb()
.$type<Array<"send" | "read" | "state_write" | "admin">>()
.$type<("send" | "read" | "state_write" | "admin")[]>()
.notNull()
.default([]),
/**