/** * Reads the group-sync freshness marker the pre-token Lambda fails closed on. * * The group-sync Lambda writes `lastSuccessfulSyncMs` (epoch ms) to a single * item in the sync-state table on every successful Google→Cognito sync. If that * marker is missing or older than {@link MAX_SYNC_AGE_MS}, group membership may * be stale (a revoked user could still appear entitled), so the pre-token Lambda * drops to base scopes (design.md §2.3 fail-closed; agentforce-plan §0.1 CR-5). */ import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'; /** Stale threshold: 30 min. A 5-min sync cadence means >6 missed runs is a fault. */ export const MAX_SYNC_AGE_MS = 30 * 60 * 1000; /** Fixed partition key of the singleton sync-state item. */ export const SYNC_STATE_PK = 'group-sync'; let cached: DynamoDBDocumentClient | undefined; function doc(): DynamoDBDocumentClient { cached ??= DynamoDBDocumentClient.from(new DynamoDBClient({})); return cached; } /** * Fetch the last successful sync timestamp (epoch ms), or `null` if the marker * is absent or unreadable. A read failure resolves to `null` (treated as stale) * so an outage of the sync-state table fails CLOSED, never open. */ export async function readLastSuccessfulSyncMs( tableName: string, client: DynamoDBDocumentClient = doc(), ): Promise { try { const res = await client.send( new GetCommand({ TableName: tableName, Key: { pk: SYNC_STATE_PK } }), ); const ts = res.Item?.['lastSuccessfulSyncMs']; return typeof ts === 'number' && Number.isFinite(ts) ? ts : null; } catch { return null; } } /** True iff `lastSyncMs` is present and within {@link MAX_SYNC_AGE_MS} of `nowMs`. */ export function isSyncFresh( lastSyncMs: number | null, nowMs: number, maxAgeMs: number = MAX_SYNC_AGE_MS, ): boolean { return lastSyncMs !== null && nowMs - lastSyncMs <= maxAgeMs && lastSyncMs <= nowMs; }