{"version":3,"file":"criteria.d.ts","sourceRoot":"","sources":["../../../../src/core/long-horizon/adaptive/criteria.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACzC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,aAAa,CAElF;AAyBD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,QAAQ,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO;;;EAkClC;AAgCD,mFAAmF;AACnF,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,SAAS,cAAc,EAAE,GACjC,aAAa,CAKf;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa;;;;;;;EAUpD","sourcesContent":["/**\n * Evidence-backed success criteria.\n *\n * Machine-readable acceptance criteria. A required criterion can never be\n * silently waived; a criterion can be marked satisfied only when its required\n * evidence is present. Test claims require test artifacts, publication claims\n * require registry verification, user-observation criteria remain pending until\n * real user evidence is supplied, and the model cannot fabricate them.\n */\n\nimport type { AcceptanceCriterion } from \"./types.js\";\n\nexport interface EvidenceRecord {\n\tevidenceId: string;\n\tcategory: string;\n\tdescription?: string;\n}\n\nexport interface CriteriaState {\n\tcriteria: readonly AcceptanceCriterion[];\n}\n\nexport function createCriteriaState(criteria: AcceptanceCriterion[]): CriteriaState {\n\treturn { criteria: Object.freeze(criteria.map(freezeCriterion)) };\n}\n\nfunction freezeCriterion(c: AcceptanceCriterion): AcceptanceCriterion {\n\treturn Object.freeze({\n\t\tcriterionId: String(c.criterionId),\n\t\tdescription: String(c.description),\n\t\trequired: Boolean(c.required),\n\t\tevidenceRequirements: Object.freeze([...c.evidenceRequirements]),\n\t\tstatus: c.status,\n\t\tevidenceIds: Object.freeze([...(c.evidenceIds ?? [])]),\n\t\twaiverAuthority: c.waiverAuthority,\n\t});\n}\n\n/** Evidence categories that can satisfy a given requirement category. */\nconst EVIDENCE_CATEGORY_REQUIREMENT: Record<string, ReadonlySet<string>> = {\n\ttest: new Set([\"test_artifact\"]),\n\tpublication: new Set([\"registry_verification\"]),\n\tgit: new Set([\"git_evidence\"]),\n\tprocess: new Set([\"process_identity\"]),\n\tresearch: new Set([\"citation\"]),\n\ttransaction: new Set([\"transaction_confirmation\"]),\n\tfile: new Set([\"file_hash\"]),\n};\n\n/**\n * Attempt to satisfy a pending criterion from supplied evidence records.\n * Returns the new status and whether it changed (the model cannot self-mark\n * satisfied without matching evidence).\n */\nexport function evaluateCriteriaSatisfaction(\n\tstate: CriteriaState,\n\tevidence: readonly EvidenceRecord[],\n\t_options: { userId?: string } = {},\n) {\n\tconst updated: AcceptanceCriterion[] = [];\n\tlet changed = false;\n\n\tfor (const criterion of state.criteria) {\n\t\tlet next = criterion;\n\n\t\tif (criterion.status === \"pending\") {\n\t\t\tconst satisfied = evidenceSatisfies(criterion, evidence);\n\t\t\tif (satisfied) {\n\t\t\t\tnext = freezeCriterion({\n\t\t\t\t\t...criterion,\n\t\t\t\t\tstatus: \"satisfied\",\n\t\t\t\t\tevidenceIds: Object.freeze(\n\t\t\t\t\t\t[...criterion.evidenceIds, ...matchedEvidenceIds(criterion, evidence)].filter(\n\t\t\t\t\t\t\t(v, i, a) => a.indexOf(v) === i,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t} else if (criterion.status === \"waived\") {\n\t\t\t// A waiver requires an authority; model output is never an authority.\n\t\t\tif (!criterion.waiverAuthority || criterion.waiverAuthority === \"model\") {\n\t\t\t\tnext = freezeCriterion({ ...criterion, status: \"blocked\" });\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\n\t\tupdated.push(next);\n\t}\n\n\treturn { state: createCriteriaState(updated), changed };\n}\n\nfunction evidenceSatisfies(criterion: AcceptanceCriterion, evidence: readonly EvidenceRecord[]): boolean {\n\tif (criterion.evidenceRequirements.length === 0) {\n\t\t// A criterion with explicit evidence requirements is not satisfiable with\n\t\t// none. Policy: absence of a requirement means no evidence gate.\n\t\treturn false;\n\t}\n\tconst availableCategories = new Set(evidence.map((e) => e.category));\n\tfor (const requirement of criterion.evidenceRequirements) {\n\t\tconst allowed = EVIDENCE_CATEGORY_REQUIREMENT[requirement];\n\t\tif (allowed) {\n\t\t\tlet matched = false;\n\t\t\tfor (const cat of availableCategories) {\n\t\t\t\tif (allowed.has(cat)) {\n\t\t\t\t\tmatched = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!matched) return false;\n\t\t} else {\n\t\t\t// Unknown requirement category — require at least one matching evidence.\n\t\t\tif (evidence.length === 0) return false;\n\t\t}\n\t}\n\treturn evidence.length > 0;\n}\n\nfunction matchedEvidenceIds(_criterion: AcceptanceCriterion, evidence: readonly EvidenceRecord[]): string[] {\n\treturn evidence.map((e) => e.evidenceId);\n}\n\n/** Mark a criterion satisfied only with matching evidence; reject prose claims. */\nexport function trySatisfyCriterion(\n\tstate: CriteriaState,\n\tcriterionId: string,\n\tevidence: readonly EvidenceRecord[],\n): CriteriaState {\n\tconst result = evaluateCriteriaSatisfaction(state, evidence);\n\tconst idx = result.state.criteria.findIndex((c) => c.criterionId === criterionId);\n\tif (idx === -1) return state;\n\treturn result.state;\n}\n\n/** Count satisfied vs pending/failed/blocked for completion evaluation. */\nexport function criterionSummary(state: CriteriaState) {\n\tconst summary = { satisfied: 0, pending: 0, failed: 0, blocked: 0, waived: 0, total: state.criteria.length };\n\tfor (const c of state.criteria) {\n\t\tif (c.status === \"satisfied\") summary.satisfied += 1;\n\t\telse if (c.status === \"pending\") summary.pending += 1;\n\t\telse if (c.status === \"failed\") summary.failed += 1;\n\t\telse if (c.status === \"blocked\") summary.blocked += 1;\n\t\telse if (c.status === \"waived\") summary.waived += 1;\n\t}\n\treturn summary;\n}\n"]}