feat(broker): record daemon idempotency fields on message_queue
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:
@@ -0,0 +1,18 @@
|
||||
-- Daemon idempotency fields on message_queue (v0.9.0 daemon spec §4.2 / §4.4).
|
||||
--
|
||||
-- Adds two nullable columns so the daemon can attach its caller-supplied
|
||||
-- `client_message_id` and the canonical `request_fingerprint` (sha256 hex
|
||||
-- of the canonical request shape) to every send.
|
||||
--
|
||||
-- Both columns are nullable for backward compatibility — legacy traffic
|
||||
-- from `claudemesh launch` and the dashboard chat doesn't carry them yet.
|
||||
-- Sprint 7 (full broker hardening) will:
|
||||
-- - add a partial unique index `(mesh_id, client_message_id) WHERE
|
||||
-- client_message_id IS NOT NULL` once we're ready to enforce dedupe.
|
||||
-- - introduce the `mesh.client_message_dedupe` table for atomic accept.
|
||||
-- Until then, recording the values lets the broker echo them back on push
|
||||
-- so daemon-side inboxes can dedupe correctly even with multiple senders.
|
||||
|
||||
ALTER TABLE "mesh"."message_queue"
|
||||
ADD COLUMN "client_message_id" text,
|
||||
ADD COLUMN "request_fingerprint" text;
|
||||
@@ -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([]),
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user