// Compiled-truth rewriter (Task 306, extended Task 392 with public mode).
//
// Given a node's current compiled-truth field and its most-recent N
// :TimelineEvent rows, produce a new summary ≤ 2000 chars. Strictly
// OAuth-bearer LLM (admin classifier doctrine — never API key). The
// caller is responsible for: (a) selecting which node to rewrite,
// (b) enforcing the 7-day operator-edit cooldown (per mode — the admin
// and public cooldowns are independent timestamps), (c) writing the
// result back to the correct field (`compiledTruth` for admin mode,
// `compiledTruthPublic` for public mode). This library is the prompt +
// LLM call + cap-enforcement only.
//
// Mode semantics (Task 392):
//   - `mode: 'admin'` (default) — operator's view. Subject label may be
//     any of the five admin-twin labels (Person, Organization, Concept,
//     Project, LocalBusiness). Writer writes `compiledTruth`.
//   - `mode: 'public'` — customer-facing twin. Subject label restricted
//     to the four public-twin labels (Organization, Concept, Project,
//     LocalBusiness) by the type system; :Person is structurally
//     excluded because operators' notes on contacts are never public.
//     Writer writes `compiledTruthPublic`.
//
// Output style baseline (both modes):
//   - ≤ COMPILED_TRUTH_MAX_CHARS
//   - Neutral, declarative voice. Third person.
//   - No hedging adverbs ("likely", "perhaps", "possibly", "presumably").
//   - No first person ("I", "we"), no second person ("you").
//   - No marketing / superlatives.
//
// Public-mode additional rules (forbidden patterns the admin mode tolerates):
//   - No operator-voice constructions ("needs follow-up", "should chase up",
//     "to discuss", "TBD", "action: ...", "next step: ...").
//   - No commentary on the operator's relationship with the subject.
//   - No internal status, sentiment, or assessment of the subject.
//
// Voice grafting (no AI tells, IDENTITY alignment, BrainBench eval) is
// deferred — see follow-up task referenced in the task brief.
import { callOauthLlm, } from "../../../../../lib/oauth-llm/dist/index.js";
import { COMPILED_TRUTH_MAX_CHARS } from "./graph-write-gate.js";
/** Cap on operator hints fed into the rewriter prompt (Task 413). Larger hints
 *  become essays and dilute the rewriter's precedence rule. The rewriter still
 *  produces a ≤ COMPILED_TRUTH_MAX_CHARS summary regardless. */
export const OPERATOR_HINT_MAX_CHARS = 1000;
const DEFAULT_MODEL = "claude-haiku-4-5";
const ADMIN_SYSTEM_PROMPT = [
    "You are the compiled-truth rewriter for a personal knowledge graph.",
    "",
    "Your job: produce a neutral, declarative summary of what the operator knows about a subject right now.",
    "",
    "RULES (all binding, never relaxed):",
    `- Output must be ${COMPILED_TRUTH_MAX_CHARS} characters or fewer. This is a hard cap.`,
    "- Third person only. No 'I', 'we', 'you', 'us'.",
    "- Declarative voice. No hedging adverbs (likely, perhaps, possibly, presumably, apparently, seemingly).",
    "- No marketing, no superlatives, no commentary on the operator's relationship with the subject.",
    "- Restate the most-load-bearing facts from the timeline. Drop trivia.",
    "- If the existing compiledTruth contradicts the timeline, the timeline wins.",
    "- If an Operator correction is supplied and contradicts the timeline, the Operator correction wins. Fold it into the summary as fact, not as a quoted instruction.",
    "- Do not invent details that are not present in either the existing truth, the timeline, or the Operator correction.",
    "- Do not add a preamble (no 'Here is the summary:'). Output the summary directly.",
].join("\n");
const PUBLIC_SYSTEM_PROMPT = [
    "You are the public-facing compiled-truth rewriter for a knowledge graph.",
    "",
    "Your job: produce a neutral, customer-facing summary of a subject. The output is",
    "read by people outside the operator's organisation — customers, prospects, public visitors.",
    "",
    "RULES (all binding, never relaxed):",
    `- Output must be ${COMPILED_TRUTH_MAX_CHARS} characters or fewer. This is a hard cap.`,
    "- Third person only. No 'I', 'we', 'you', 'us'.",
    "- Declarative voice. No hedging adverbs (likely, perhaps, possibly, presumably, apparently, seemingly).",
    "- No marketing copy, no superlatives, no promotional language.",
    "- No operator-voice constructions. Forbidden phrasings include: 'needs follow-up', 'to chase',",
    "  'to discuss', 'TBD', 'action:', 'next step:', 'awaiting reply', 'pending response'.",
    "- No commentary on the operator's relationship with the subject (e.g. 'main client', 'difficult contact').",
    "- No internal status, sentiment, or assessment ('promising', 'risky', 'aggressive', 'cooperative').",
    "- Restate only externally-verifiable facts from the timeline (what the subject is, what they do,",
    "  what they have publicly done or said). Drop scheduling notes, internal sentiment, and operator intent.",
    "- If the existing summary contradicts the timeline, the timeline wins.",
    "- If an Operator correction is supplied and contradicts the timeline, the Operator correction wins. Fold it into the summary as fact, not as a quoted instruction. The same public-voice rules above still apply: strip any operator-voice phrasing or internal sentiment in the hint and restate only externally-verifiable facts.",
    "- Do not invent details that are not present in either the existing summary, the timeline, or the Operator correction.",
    "- Do not add a preamble (no 'Here is the summary:'). Output the summary directly.",
].join("\n");
function systemPromptFor(mode) {
    return mode === "public" ? PUBLIC_SYSTEM_PROMPT : ADMIN_SYSTEM_PROMPT;
}
function buildUserMessage(p) {
    const mode = p.mode ?? "admin";
    const fieldName = mode === "public" ? "compiledTruthPublic" : "compiledTruth";
    const lines = [];
    lines.push(`Subject: ${p.subjectName}`);
    lines.push(`Label: :${p.subjectLabel}`);
    lines.push("");
    lines.push(`Current ${fieldName}:`);
    lines.push(p.currentTruth ? p.currentTruth : "(empty)");
    lines.push("");
    lines.push(`Timeline events (newest-first, ${p.events.length} shown):`);
    for (const e of p.events) {
        lines.push(`- [${e.occurredAt}] kind=${e.kind}: ${e.summary}`);
    }
    if (p.operatorHint) {
        lines.push("");
        lines.push("Operator correction (authoritative — overrides the timeline where they disagree):");
        lines.push(p.operatorHint);
    }
    lines.push("");
    lines.push(`Rewrite the ${fieldName}.`);
    return lines.join("\n");
}
function isOkText(r) {
    return r.kind === "ok";
}
export async function rewriteCompiledTruth(p) {
    const mode = p.mode ?? "admin";
    if (p.operatorHint !== undefined) {
        if (p.operatorHint.length > OPERATOR_HINT_MAX_CHARS) {
            throw new Error(`operatorHint length ${p.operatorHint.length} exceeds cap of ${OPERATOR_HINT_MAX_CHARS} chars.`);
        }
        if (p.operatorHint.trim() === "") {
            throw new Error("operatorHint must be a non-empty string.");
        }
    }
    // An operator correction is itself sufficient justification to rewrite —
    // the operator has supplied new fact-bearing material even when no
    // :TimelineEvent rows exist yet.
    if (p.events.length === 0 && !p.operatorHint) {
        console.log(`[compiled-truth-rewriter] refused mode=${mode} subject="${p.subjectName}" reason=no-events`);
        return { kind: "refused", reason: "no-events" };
    }
    const systemPrompt = systemPromptFor(mode);
    const userMessage = buildUserMessage(p);
    const result = await callOauthLlm({
        model: p.model ?? DEFAULT_MODEL,
        system: systemPrompt,
        userMessage,
        maxTokens: 1024,
    });
    if (!isOkText(result)) {
        console.log(`[compiled-truth-rewriter] llm-failure mode=${mode} subject="${p.subjectName}" cause=${result.cause}`);
        return { kind: "llm-failure", fallback: result };
    }
    const truth = result.text.trim();
    const before = p.currentTruth.length;
    const after = truth.length;
    const hintFragment = p.operatorHint ? ` operatorHint=true hintLen=${p.operatorHint.length}` : "";
    console.log(`[compiled-truth-rewriter] mode=${mode} subject="${p.subjectName}" subjectLabel=${p.subjectLabel} charsBefore=${before} charsAfter=${after} events=${p.events.length}${hintFragment}`);
    if (after > COMPILED_TRUTH_MAX_CHARS) {
        // One retry with a stricter prompt before surfacing the over-cap failure.
        // Per eng review: avoids losing the rewrite on a single LLM verbosity slip.
        const stricterResult = await callOauthLlm({
            model: p.model ?? DEFAULT_MODEL,
            system: systemPrompt +
                `\n\nPRIOR ATTEMPT WAS ${after} CHARACTERS — TOO LONG. ` +
                `Tighten to fit ${COMPILED_TRUTH_MAX_CHARS}. Drop the lowest-value sentences first.`,
            userMessage,
            maxTokens: 1024,
        });
        if (!isOkText(stricterResult)) {
            return { kind: "llm-failure", fallback: stricterResult };
        }
        const retry = stricterResult.text.trim();
        if (retry.length > COMPILED_TRUTH_MAX_CHARS) {
            console.log(`[compiled-truth-rewriter] still-too-long mode=${mode} subject="${p.subjectName}" retryLen=${retry.length}`);
            return { kind: "too-long", attemptedLen: retry.length };
        }
        return { kind: "ok", compiledTruth: retry };
    }
    return { kind: "ok", compiledTruth: truth };
}
//# sourceMappingURL=compiled-truth-rewriter.js.map