feat(iios): projection-cursor observability on /metrics + smoke (P9)

Adds a global `projections` section to GET /metrics exposing each cursor's
position (last_event_id/last_offset), determinism checksum, and lagMs.
smoke-projection.mjs injects events and asserts the projection's ordered
high-water-mark advances with a 64-hex rolling checksum (KG-06).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 02:48:14 +05:30
parent 2424739e3a
commit 67da42a103
2 changed files with 88 additions and 0 deletions
@@ -2,6 +2,7 @@ import { BadRequestException, Controller, Get, Headers } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver';
import { SessionVerifier } from '../platform/session.verifier';
import { ProjectionCursorService } from '../projection/projection-cursor.service';
/**
* Tenant-scoped metrics (P9 observability, JSON). Returns the CALLER's tenant
@@ -15,6 +16,7 @@ export class MetricsController {
private readonly prisma: PrismaService,
private readonly actors: ActorResolver,
private readonly session: SessionVerifier,
private readonly cursors: ProjectionCursorService,
) {}
@Get()
@@ -40,11 +42,23 @@ export class MetricsController {
const oldest = await this.prisma.iiosOutboxEvent.findFirst({ where: { status: { not: 'PUBLISHED' } }, orderBy: { createdAt: 'asc' } });
const oldestPendingMs = oldest ? Date.now() - new Date(oldest.createdAt).getTime() : 0;
// Projection cursors (global ops signal — position + determinism checksum + lag).
const projections = (await this.cursors.list()).map((c) => ({
projectionName: c.projectionName,
sourceTopic: c.sourceTopic,
partitionKey: c.partitionKey,
lastEventId: c.lastEventId,
lastOffset: c.lastOffset,
checksum: c.checksum,
lagMs: Date.now() - new Date(c.updatedAt).getTime(),
}));
return {
ts: new Date().toISOString(),
scope: scope ? { orgId: scope.orgId, appId: scope.appId, tenantId: scope.tenantId, cellId: scope.cellId } : null,
tenant,
relay: { outboxPending, oldestPendingMs },
projections,
};
}