{"version":3,"file":"ledger-summary.d.ts","sourceRoot":"","sources":["../../../src/core/long-horizon/ledger-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,iCAAiC,EAA6B,MAAM,yBAAyB,CAAC;AACvG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAM3G;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,OAAO,iCAAqE,CAAC;AAMlH;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC7C,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,mBAAmB,EAC3B,iBAAiB,EAAE,wBAAwB,GACzC,aAAa,CAkHf","sourcesContent":["/**\n * Ledger Summary — deterministic mission status snapshot.\n *\n * Computes comprehensive summaries from contract + ledger.\n * Pure, no side effects, no provider dependency.\n *\n * LH-1 Hardened:\n *   - Authoritative summary requires a TrustedValidationContext.\n *   - Trust checks are always enforced (not optional).\n *   - completionCandidate only available through trusted path.\n *   - Structural inspection path cannot produce completionCandidate.\n */\n\nimport { computeMissionContractDigest } from \"./contract-digest.js\";\nimport { inspectRequirementLedgerStructure, validateRequirementLedger } from \"./requirement-ledger.js\";\nimport type { TrustedValidationContext } from \"./trusted-context.js\";\nimport { _getBoundContractDigest, isTrustedValidationContext } from \"./trusted-context.js\";\nimport type { LedgerSummary, MissionContractV1, RequirementLedgerV1, WorkstreamSummary } from \"./types.js\";\n\n// =============================================================================\n// Untrusted structural inspection — never produces completionCandidate=true\n// =============================================================================\n\n/**\n * Inspect ledger structure without trust verification.\n *\n * completionCandidate is always \"unavailable\". This path is for\n * generic CLI and untrusted callers. Use deriveRequirementLedgerSummary\n * with a TrustedValidationContext for authoritative completion.\n */\nexport const inspectLedgerStructure: typeof inspectRequirementLedgerStructure = inspectRequirementLedgerStructure;\n\n// =============================================================================\n// Authoritative summary — REQUIRES TrustedValidationContext\n// =============================================================================\n\n/**\n * Derive a deterministic ledger summary with full trust verification.\n *\n * The validationContext is MANDATORY. Trust and freshness checks are always enforced.\n *\n * completionCandidate is true only when:\n *   - Ledger passes structural and trust validation\n *   - Every applicable requirement == SATISFIED\n *   - All privileged principals verified against the trust registry\n *   - All capabilities verified\n *   - No stale post-regression satisfaction\n */\nexport function deriveRequirementLedgerSummary(\n\tcontract: MissionContractV1,\n\tledger: RequirementLedgerV1,\n\tvalidationContext: TrustedValidationContext,\n): LedgerSummary {\n\t// === RUNTIME CONTEXT GUARD ===\n\t// A genuine TrustedValidationContext is MANDATORY for authoritative summary.\n\t// Plain objects, Proxies, JSON-derived objects, and Object.assign copies\n\t// are rejected at runtime.\n\tif (!isTrustedValidationContext(validationContext)) {\n\t\t// Return a summary that unambiguously reports completion as unavailable.\n\t\t// Typed callers should use the guard before calling; this is defense-in-depth.\n\t\treturn {\n\t\t\tmissionId: contract.missionId,\n\t\t\tcontractRevision: contract.revision,\n\t\t\tcontractDigest: ledger.contractDigest,\n\t\t\tledgerRevision: ledger.revision,\n\t\t\ttotalRequirements: contract.requirements.length,\n\t\t\tapplicableRequirements: 0,\n\t\t\tstateCounts: {},\n\t\t\texplicitCount: 0,\n\t\t\tinferredCount: 0,\n\t\t\tworkstreamSummaries: [],\n\t\t\tblockedRequirements: [],\n\t\t\tfailedRequirements: [],\n\t\t\trequirementsLackingAuthoritativeEvidence: [],\n\t\t\tcompletionCandidate: false,\n\t\t\tcompletionBlockers: [\"TRUSTED_VALIDATION_CONTEXT_REQUIRED\"],\n\t\t};\n\t}\n\n\tconst completionBlockers: string[] = [];\n\n\t// === CONTRACT-BINDING GUARD ===\n\t// Every genuine TrustedValidationContext is bound to exactly one\n\t// Mission Contract digest. A context minted for Contract A must not\n\t// validate Contract B.\n\tconst contextDigest2 = _getBoundContractDigest(validationContext);\n\tif (contextDigest2 !== undefined) {\n\t\tconst contractDigest2 = computeMissionContractDigest(contract);\n\t\tif (contextDigest2 !== contractDigest2) {\n\t\t\tcompletionBlockers.push(\n\t\t\t\t`TRUSTED_VALIDATION_CONTEXT_CONTRACT_MISMATCH: context bound to ${contextDigest2} but contract computes ${contractDigest2}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Always enforce trust and freshness\n\tconst ledgerValid = validateRequirementLedger(contract, ledger, validationContext);\n\n\tif (!ledgerValid.ok) {\n\t\tcompletionBlockers.push(`Ledger validation failed: ${ledgerValid.error}`);\n\t}\n\n\tconst stateCounts: LedgerSummary[\"stateCounts\"] = {};\n\tlet applicableCount = 0;\n\n\tfor (const entry of ledger.requirements) {\n\t\tif (entry.initialNotApplicable) continue;\n\t\tapplicableCount++;\n\t\tconst s = entry.status;\n\t\tstateCounts[s] = (stateCounts[s] ?? 0) + 1;\n\t}\n\n\tlet explicitCount = 0;\n\tlet inferredCount = 0;\n\tfor (const req of contract.requirements) {\n\t\tconst entry = ledger.requirements.find((r) => r.requirementId === req.id);\n\t\tif (entry?.initialNotApplicable) continue;\n\t\tif (req.kind === \"EXPLICIT\") explicitCount++;\n\t\telse inferredCount++;\n\t}\n\n\tconst wsMap = new Map<string, { title: string; total: number; satisfied: number }>();\n\tfor (const ws of contract.workstreams) {\n\t\twsMap.set(ws.id, { title: ws.title, total: 0, satisfied: 0 });\n\t}\n\tfor (const req of contract.requirements) {\n\t\tconst entry = ledger.requirements.find((r) => r.requirementId === req.id);\n\t\tif (entry?.initialNotApplicable) continue;\n\t\tconst ws = wsMap.get(req.workstreamId);\n\t\tif (ws) {\n\t\t\tws.total++;\n\t\t\tif (entry?.status === \"SATISFIED\") ws.satisfied++;\n\t\t}\n\t}\n\tconst workstreamSummaries: WorkstreamSummary[] = Array.from(wsMap.entries()).map(([id, data]) => ({\n\t\tworkstreamId: id,\n\t\ttitle: data.title,\n\t\ttotalRequirements: data.total,\n\t\tsatisfiedCount: data.satisfied,\n\t}));\n\n\tconst blockedRequirements = ledger.requirements.filter((r) => r.status === \"BLOCKED\").map((r) => r.requirementId);\n\tconst failedRequirements = ledger.requirements.filter((r) => r.status === \"FAILED\").map((r) => r.requirementId);\n\n\tconst requirementsLackingAuthoritativeEvidence = findRequirementsLackingEvidence(contract, ledger);\n\n\t// Completion candidate: always computed with trust\n\tconst completionCandidate = computeCompletionCandidate(contract, ledger, ledgerValid.ok, completionBlockers);\n\n\treturn {\n\t\tmissionId: contract.missionId,\n\t\tcontractRevision: contract.revision,\n\t\tcontractDigest: ledger.contractDigest,\n\t\tledgerRevision: ledger.revision,\n\t\ttotalRequirements: contract.requirements.length,\n\t\tapplicableRequirements: applicableCount,\n\t\tstateCounts,\n\t\texplicitCount,\n\t\tinferredCount,\n\t\tworkstreamSummaries,\n\t\tblockedRequirements,\n\t\tfailedRequirements,\n\t\trequirementsLackingAuthoritativeEvidence,\n\t\tcompletionCandidate,\n\t\tcompletionBlockers: completionBlockers.length > 0 ? completionBlockers : undefined,\n\t};\n}\n\n// =============================================================================\n// Completion Candidate — hardened\n// =============================================================================\n\nfunction computeCompletionCandidate(\n\tcontract: MissionContractV1,\n\tledger: RequirementLedgerV1,\n\tledgerValid: boolean,\n\tblockers: string[],\n): boolean {\n\tif (!ledgerValid) return false;\n\n\tfor (const entry of ledger.requirements) {\n\t\tif (entry.initialNotApplicable) continue;\n\t\tif (entry.status !== \"SATISFIED\") {\n\t\t\tblockers.push(`Requirement ${entry.requirementId} is ${entry.status}, not SATISFIED`);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfor (const req of contract.requirements) {\n\t\tconst entry = ledger.requirements.find((r) => r.requirementId === req.id);\n\t\tif (!entry || entry.initialNotApplicable) continue;\n\n\t\tconst hasAuthEvidence = ledger.evidence.some(\n\t\t\t(ev) => ev.requirementIds.includes(req.id) && ev.effectiveAuthority !== \"agent-claim\" && ev.status === \"pass\",\n\t\t);\n\n\t\tif (!hasAuthEvidence) {\n\t\t\tblockers.push(`Requirement ${req.id} is SATISFIED but lacks authoritative evidence`);\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (const criterion of req.acceptanceCriteria) {\n\t\t\tconst criterionEvidence = ledger.evidence.some(\n\t\t\t\t(ev) =>\n\t\t\t\t\tev.requirementIds.includes(req.id) &&\n\t\t\t\t\tev.criterionIds.includes(criterion.id) &&\n\t\t\t\t\tev.effectiveAuthority !== \"agent-claim\" &&\n\t\t\t\t\tev.status === \"pass\",\n\t\t\t);\n\n\t\t\tif (!criterionEvidence) {\n\t\t\t\tblockers.push(`Requirement ${req.id}: criterion \"${criterion.id}\" has no authoritative evidence`);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif (entry.latestRegressionRevision !== undefined) {\n\t\t\tconst lastSatTx = [...ledger.transitions]\n\t\t\t\t.reverse()\n\t\t\t\t.find((t) => t.requirementId === req.id && t.toStatus === \"SATISFIED\");\n\n\t\t\tif (lastSatTx) {\n\t\t\t\tlet allFresh = true;\n\t\t\t\tfor (const criterion of req.acceptanceCriteria) {\n\t\t\t\t\tconst hasFresh = lastSatTx.evidenceIds.some((evId) => {\n\t\t\t\t\t\tconst ev = ledger.evidence.find((e) => e.id === evId);\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tev?.criterionIds.includes(criterion.id) && ev.addedAtRevision > entry.latestRegressionRevision!\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!hasFresh) {\n\t\t\t\t\t\tallFresh = false;\n\t\t\t\t\t\tblockers.push(\n\t\t\t\t\t\t\t`Requirement ${req.id}: criterion \"${criterion.id}\" uses stale evidence after regression`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!allFresh) return false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n// =============================================================================\n// Evidence lacking detection\n// =============================================================================\n\nfunction findRequirementsLackingEvidence(contract: MissionContractV1, ledger: RequirementLedgerV1): string[] {\n\tconst lacking: string[] = [];\n\n\tfor (const req of contract.requirements) {\n\t\tconst entry = ledger.requirements.find((r) => r.requirementId === req.id);\n\t\tif (!entry || entry.initialNotApplicable) continue;\n\t\tif (entry.status !== \"SATISFIED\") continue;\n\n\t\tconst hasAuthEvidence = ledger.evidence.some(\n\t\t\t(ev) => ev.requirementIds.includes(req.id) && ev.effectiveAuthority !== \"agent-claim\" && ev.status === \"pass\",\n\t\t);\n\n\t\tif (!hasAuthEvidence) {\n\t\t\tlacking.push(req.id);\n\t\t}\n\t}\n\n\treturn lacking;\n}\n"]}