import type { CinemaGenerationLedgerArtifact } from './cinema-types.js'; export interface CinemaRetryPolicy { maximumAttemptsPerShot: number; maximumRepeatedFailureTag: number; } export interface CinemaRetryPolicyDecision { shotId: string; disposition: 'retry-eligible' | 'redesign-required'; attemptCount: number; parentAttemptId: string | null; blockingFailureTags: string[]; reason: string; } export function evaluateCinemaRetryPolicy( ledger: CinemaGenerationLedgerArtifact, shotId: string, policy: CinemaRetryPolicy, ): CinemaRetryPolicyDecision { if (!Number.isInteger(policy.maximumAttemptsPerShot) || policy.maximumAttemptsPerShot < 1) throw new Error('Cinema maximumAttemptsPerShot must be a positive integer'); if (!Number.isInteger(policy.maximumRepeatedFailureTag) || policy.maximumRepeatedFailureTag < 1) throw new Error('Cinema maximumRepeatedFailureTag must be a positive integer'); const attempts = ledger.attempts.filter((attempt) => attempt.shotId === shotId).sort((left, right) => left.preparedAt.localeCompare(right.preparedAt) || left.attemptId.localeCompare(right.attemptId)); const counts = new Map(); for (const attempt of attempts) for (const tag of new Set(attempt.failureTags)) counts.set(tag, (counts.get(tag) ?? 0) + 1); const blockingFailureTags = [...counts].filter(([, count]) => count >= policy.maximumRepeatedFailureTag).map(([tag]) => tag).sort(); const parentAttemptId = attempts.at(-1)?.attemptId ?? null; if (attempts.length >= policy.maximumAttemptsPerShot) { return { shotId, disposition: 'redesign-required', attemptCount: attempts.length, parentAttemptId, blockingFailureTags, reason: `Attempt ceiling ${policy.maximumAttemptsPerShot} reached; an authority-verified redesign decision must name one controlled variable before any new attempt.` }; } if (blockingFailureTags.length > 0) { return { shotId, disposition: 'redesign-required', attemptCount: attempts.length, parentAttemptId, blockingFailureTags, reason: `Repeated failure tag ceiling reached (${blockingFailureTags.join(', ')}); blind retry is forbidden.` }; } return { shotId, disposition: 'retry-eligible', attemptCount: attempts.length, parentAttemptId, blockingFailureTags: [], reason: 'A single-variable child attempt may be prepared under the existing lineage validator.' }; }