// engine/reviewer-tokens/verifier.ts — Token verification for SPEC-722
import { loadSecret, canonicalJson, hmacVerify } from './signer.js';
import { decodeToken } from './issuer.js';
/** Runtime check: is the decoded payload a planner token? */
function isPlannerPayload(p) {
    return p !== null && typeof p === 'object' && p.kind === 'planner';
}
/** Runtime check: is the decoded payload a reviewer token? */
function isReviewerPayload(p) {
    return p !== null && typeof p === 'object' && p.kind === 'reviewer';
}
/**
 * Verify a planner token: checks signature and expiry.
 * Returns the decoded payload if valid, or a rejection reason.
 */
export async function verifyPlannerToken(projectPath, raw) {
    const decoded = decodeToken(raw);
    if (decoded === null) {
        return { valid: false, reason: 'Planner token is malformed or cannot be decoded.' };
    }
    if (!isPlannerPayload(decoded.payload)) {
        return { valid: false, reason: 'Token is not a planner token.' };
    }
    const payload = decoded.payload;
    // Check expiry.
    if (new Date(payload.expiresAt) < new Date()) {
        return { valid: false, reason: 'Planner token has expired.' };
    }
    // Verify signature.
    const secret = await loadSecret(projectPath);
    const canonical = canonicalJson(payload);
    if (!hmacVerify(secret, canonical, decoded.signature)) {
        return { valid: false, reason: 'Planner token signature is invalid.' };
    }
    return { valid: true, payload };
}
/**
 * Verify a reviewer token against the planner token.
 *
 * Rules enforced (SPEC-722):
 *  1. Both tokens must have valid HMAC signatures.
 *  2. Reviewer token must not be expired.
 *  3. reviewerToken.sessionId !== plannerToken.sessionId  (no self-approval).
 *  4. reviewerToken.modelId !== plannerToken.modelId  OR a multiTeammateReviewId is provided.
 */
export async function verifyReviewerToken(projectPath, reviewerTokenRaw, plannerTokenRaw, opts) {
    // --- Decode and verify planner token ---
    const plannerCheck = await verifyPlannerToken(projectPath, plannerTokenRaw);
    if (!plannerCheck.valid || plannerCheck.payload === undefined) {
        return { valid: false, reason: `Planner token invalid: ${plannerCheck.reason ?? 'unknown'}` };
    }
    const plannerPayload = plannerCheck.payload;
    // --- Decode reviewer token ---
    const decoded = decodeToken(reviewerTokenRaw);
    if (decoded === null) {
        return { valid: false, reason: 'Reviewer token is malformed or cannot be decoded.' };
    }
    if (!isReviewerPayload(decoded.payload)) {
        return { valid: false, reason: 'Token is not a reviewer token.' };
    }
    const payload = decoded.payload;
    // Check expiry.
    if (new Date(payload.expiresAt) < new Date()) {
        return { valid: false, reason: 'Reviewer token has expired (TTL 7 days).' };
    }
    // Verify reviewer token signature.
    const secret = await loadSecret(projectPath);
    const canonical = canonicalJson(payload);
    if (!hmacVerify(secret, canonical, decoded.signature)) {
        return { valid: false, reason: 'Reviewer token signature is invalid.' };
    }
    // --- Rule 3: same session = self-approval attempt ---
    if (payload.sessionId === plannerPayload.sessionId) {
        return {
            valid: false,
            reason: 'Self-approval detected: the reviewer token was issued in the same session as the planner token. ' +
                'A different Opus session must be used to review.',
        };
    }
    // --- Rule 4: same model requires a multi-teammate review panel ---
    const sameModel = payload.modelId === plannerPayload.modelId;
    if (sameModel && !opts?.multiTeammateReviewId) {
        return {
            valid: false,
            reason: `Same model (${payload.modelId}) used for planning and reviewing. ` +
                'Provide a multiTeammateReviewId from a heterogeneous panel, or use a different model for review.',
        };
    }
    return { valid: true, payload };
}
/**
 * SPEC-727: Verify that a reviewer token is bound to the correct planner token.
 *
 * The reviewer token embeds the planner's sessionId (as `plannerSessionId`).
 * This function decodes the reviewer token and compares its embedded plannerSessionId
 * to the sessionId in `plannerTokenOnSpec`.
 *
 * Does NOT verify HMAC here — callers must call `verifyReviewerToken` for full verification.
 * This function is a lightweight pair-match check to guard disagreement counter increments.
 */
export function verifyTokenPair(reviewerToken, plannerTokenOnSpec) {
    // Decode reviewer token (no HMAC — lightweight structural check)
    const reviewerDecoded = decodeToken(reviewerToken);
    if (reviewerDecoded === null || !isReviewerPayload(reviewerDecoded.payload)) {
        return { ok: false, reason: 'INVALID_TOKEN_PAIR' };
    }
    const reviewerPayload = reviewerDecoded.payload;
    // Decode planner token (no HMAC — lightweight structural check)
    const plannerDecoded = decodeToken(plannerTokenOnSpec);
    if (plannerDecoded === null || !isPlannerPayload(plannerDecoded.payload)) {
        return { ok: false, reason: 'INVALID_TOKEN_PAIR' };
    }
    const plannerPayload = plannerDecoded.payload;
    // The reviewer token must embed the same sessionId as the planner token
    if (reviewerPayload.plannerSessionId !== plannerPayload.sessionId) {
        return { ok: false, reason: 'INVALID_TOKEN_PAIR' };
    }
    return { ok: true };
}
//# sourceMappingURL=verifier.js.map