feat(iios): projection-cursor ledger + ProjectionCursorService (P9)

Adds the doc-mandated IiosProjectionCursor (ordered high-water-mark +
rolling checksum per projection/topic/partition) and a reusable
ProjectionCursorService.advance(). The checksum is a deterministic fold over
the ordered event stream, so replaying the same events reproduces it — the
KG-06 proof that replay reproduces projection outcomes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 02:39:31 +05:30
parent cdb399a9c1
commit 1997fe48bb
7 changed files with 187 additions and 1 deletions
@@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "IiosProjectionCursor" (
"id" TEXT NOT NULL,
"projectionName" TEXT NOT NULL,
"sourceTopic" TEXT NOT NULL,
"partitionKey" TEXT NOT NULL,
"lastEventId" TEXT NOT NULL,
"lastOffset" INTEGER NOT NULL DEFAULT 0,
"checksum" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "IiosProjectionCursor_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "IiosProjectionCursor_partitionKey_idx" ON "IiosProjectionCursor"("partitionKey");
-- CreateIndex
CREATE UNIQUE INDEX "IiosProjectionCursor_projectionName_sourceTopic_partitionKe_key" ON "IiosProjectionCursor"("projectionName", "sourceTopic", "partitionKey");
@@ -492,6 +492,22 @@ model IiosProcessedEvent {
@@id([consumerName, eventId])
}
/// Projection replay cursor (P9, KG-06). Ordered high-water-mark + rolling checksum per
/// (projection, topic, partition) proving replay reproduces the same projection outcome.
model IiosProjectionCursor {
id String @id @default(cuid())
projectionName String
sourceTopic String
partitionKey String
lastEventId String
lastOffset Int @default(0)
checksum String
updatedAt DateTime @updatedAt
@@unique([projectionName, sourceTopic, partitionKey])
@@index([partitionKey])
}
/// Idempotent-command ledger (P9). Suppresses duplicate externally-visible mutations:
/// same (scope, command, key) replays the cached response; a different request → conflict.
model IiosIdempotencyCommand {