// engine/reviewer-tokens/issuer.ts — Token issuance for SPEC-722
import { loadSecret, canonicalJson, hmacSign } from './signer.js';
/** Token TTL: 7 days in milliseconds. */
const TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
/**
 * Serialise a signed token to a safe opaque string (base64url).
 * Format: base64url(JSON({ payload, signature }))
 */
export function encodeToken(token) {
    const json = JSON.stringify(token);
    return Buffer.from(json, 'utf-8').toString('base64url');
}
/**
 * Deserialise an opaque token string back to a signed-token object.
 * Returns null if the string is malformed.
 */
export function decodeToken(raw) {
    try {
        const json = Buffer.from(raw, 'base64url').toString('utf-8');
        const parsed = JSON.parse(json);
        if (parsed === null ||
            typeof parsed !== 'object' ||
            !('payload' in parsed) ||
            !('signature' in parsed)) {
            return null;
        }
        const obj = parsed;
        if (typeof obj.signature !== 'string') {
            return null;
        }
        return { payload: obj.payload, signature: obj.signature };
    }
    catch {
        return null;
    }
}
/**
 * Issue a planner token for the create_spec tool call.
 * The token is opaque to the LLM — it should be passed as-is to issue_reviewer_token.
 */
export async function issuePlannerToken(projectPath, specId, sessionId, modelId, host) {
    const secret = await loadSecret(projectPath);
    const now = new Date();
    const payload = {
        kind: 'planner',
        specId,
        sessionId,
        modelId,
        host,
        issuedAt: now.toISOString(),
        expiresAt: new Date(now.getTime() + TOKEN_TTL_MS).toISOString(),
    };
    const canonical = canonicalJson(payload);
    const signature = hmacSign(secret, canonical);
    return encodeToken({ payload, signature });
}
/**
 * Issue a reviewer token, bound to the planner token.
 * The reviewer token records the reviewer's session/model/host and embeds
 * the planner's sessionId and modelId for later validation.
 */
export async function issueReviewerToken(projectPath, plannerTokenRaw, sessionId, modelId, host) {
    // Decode planner token to extract binding data.
    const decoded = decodeToken(plannerTokenRaw);
    if (decoded === null) {
        throw new Error('Cannot issue reviewer token: planner token is malformed.');
    }
    const plannerPayload = decoded.payload;
    if (plannerPayload.kind !== 'planner') {
        throw new Error('Cannot issue reviewer token: provided token is not a planner token.');
    }
    const secret = await loadSecret(projectPath);
    const now = new Date();
    const payload = {
        kind: 'reviewer',
        sessionId,
        modelId,
        host,
        issuedAt: now.toISOString(),
        expiresAt: new Date(now.getTime() + TOKEN_TTL_MS).toISOString(),
        plannerSessionId: plannerPayload.sessionId,
        plannerModelId: plannerPayload.modelId,
    };
    const canonical = canonicalJson(payload);
    const signature = hmacSign(secret, canonical);
    return encodeToken({ payload, signature });
}
//# sourceMappingURL=issuer.js.map