// ─── #697 Layer-B — review-catch back-miner (the first column) ───────────────
//
// Back-mines the review-catch capability column from PR review primitives: each posted
// review finding is a CLAIM by its author; its disposition (held = accepted+fixed →
// correct, declined-as-FP → wrong, silence → unresolved-by-absence) is the RESOLUTION.
// Author-adjudicated correctness, identical scope to #670. Pure + deterministic: it
// consumes already-mined findings through an injected port (the live GitHub fetch +
// `bot-review-parser` disposition read is the CLI-side adapter, a follow-on); core stays
// network/LLM-free. No LLM-judge — the resolution is a deterministic disposition read
// (resolutionSource `disposition-thread`), so FM-b holds.
import { CapabilityClaimSchema, CapabilityResolutionSchema, deriveClaimId, } from './schema.js';
const REVIEW_CATCH_CLAIM_KIND = 'review-finding';
/** The three active paid review bots → their stable actor-id, keyed by EXACT login. */
const REVIEW_BOT_ACTOR_IDS = {
    'coderabbitai[bot]': 'coderabbit',
    'gemini-code-assist[bot]': 'gemini-code-assist',
    'greptile-apps[bot]': 'greptile',
};
/**
 * Resolve a GitHub author login to a stable Layer-B actor-id, COUPLING to existing
 * actors rather than minting a parallel scheme (#697 fold 5, corrected c.4755848293).
 * The namespace is `{ cohort agent-seats } ∪ { coderabbit, gemini-code-assist, greptile }`:
 * the three ACTIVE paid review bots (de-facto home: `doctrine/bot-protocols.md` + the
 * parity-manifest `bot-review-configs` dimension) + the cohort seats (a cohort dispatch
 * login already IS its `cohort-roles.md` id). `pr-agent`/`qodo` are research-only/dropped
 * (no shipped #670 backend registry to couple to — forward-couple only IF #670 ships), so
 * they are NOT specially mapped. Model/backend identity is intentionally NOT folded into
 * the id (so the hit-rate aggregates across model swaps).
 *
 * @param author - the GitHub login of the finding's author (bot or cohort seat).
 * @returns the stable actor-id — a known bot's canonical id, else the trimmed login.
 * @throws if `author` is empty or whitespace-only (no silent empty actor-id).
 */
export function resolveActorId(author) {
    const trimmed = author.trim();
    if (trimmed.length === 0) {
        // Fail loud: an empty/whitespace login (e.g. a deleted GH user → null author) must
        // never silently mint an empty actor-id (Tenet 4).
        throw new Error('[Totem Error] resolveActorId: author login must be a non-empty string');
    }
    // EXACT login match, NOT a prefix — so a future variant like `greptile-enterprise[bot]`
    // is not silently collapsed into `greptile` and mixed into its hit-rate (greptile P2).
    // A cohort seat login (e.g. `totem-claude`) is unknown here → returned as its own id.
    return REVIEW_BOT_ACTOR_IDS[trimmed.toLowerCase()] ?? trimmed;
}
/**
 * Mine the review-catch column from a list of findings. One claim per finding; a
 * resolution only when the finding carries a disposition (silence → unresolved by
 * absence). Deterministic: `claimId`/`resolutionId` derive from the finding's stable
 * `commentId`, so re-mining identical history yields identical rows.
 */
export function mineReviewCatch(findings) {
    const claims = [];
    const resolutions = [];
    for (const f of findings) {
        const nativeKey = `gh-review-comment:${f.commentId}`;
        const agentSource = resolveActorId(f.author);
        const claimId = deriveClaimId({
            agentSource,
            taskType: 'review-catch',
            claimKind: REVIEW_CATCH_CLAIM_KIND,
            provenanceRef: f.prRef,
            commitSha: f.commitSha,
            nativeKey,
        });
        claims.push(CapabilityClaimSchema.parse({
            claimId,
            agentSource,
            taskType: 'review-catch',
            claimKind: REVIEW_CATCH_CLAIM_KIND,
            provenance: { ref: f.prRef, commitSha: f.commitSha },
            nativeKey,
            assertedAt: f.assertedAt,
        }));
        if (f.disposition === 'accepted' || f.disposition === 'declined') {
            resolutions.push(CapabilityResolutionSchema.parse({
                resolutionId: `rc-res:${f.commentId}`,
                claimId,
                outcome: f.disposition === 'accepted' ? 'correct' : 'wrong',
                // Deterministic read of the disposition primitive — never an LLM-judge (FM-b).
                resolutionSource: 'disposition-thread',
                // `?.trim() ||` (not `??`) so an empty/whitespace evidenceRef/resolvedAt falls
                // back instead of tripping the schema's non-empty/datetime guards (GCA).
                evidenceRef: f.dispositionEvidenceRef?.trim() || nativeKey,
                resolvedAt: f.resolvedAt?.trim() || f.assertedAt,
            }));
        }
        // silence (no disposition) → no resolution row → regenerator buckets it `unresolved`.
    }
    return { claims, resolutions };
}
//# sourceMappingURL=review-catch.js.map