import { CLI_REMEDIATION_DOCS } from "../../../core/cli-remediation.js";
import { parsePolicyApproval } from "../../../core/policy.js";
import { attachPolicyMeta } from "../attach-planning-response-meta.js";
import { readIdempotencyValue } from "../mutation-utils.js";
import { draftPlanningPhaseNoteArchivedEvent, draftPlanningPhaseNoteCreatedEvent, draftPlanningPhaseNoteSuggestionCreatedEvent, draftPlanningPhaseNoteSuggestionRemovedEvent, draftPlanningPhaseNoteSuggestionUpdatedEvent, draftPlanningPhaseNoteUpdatedEvent } from "../persistence/planning-event-draft.js";
import { isPlanningGitSyncPublishActive } from "../persistence/planning-canonical-sync-domains.js";
import { phaseNoteRowToEventSnapshot } from "../task-state-events/planning-phase-note-event-utils.js";
import { PHASE_JOURNAL_MIN_KIT_USER_VERSION, PHASE_NOTE_TASK_SUGGESTIONS_MIN_KIT_USER_VERSION, PHASE_NOTE_DETAILS_MAX, PHASE_NOTE_LIST_DEFAULT_LIMIT, PHASE_NOTE_LIST_MAX_LIMIT, PHASE_NOTE_PRIORITIES, PHASE_NOTE_REFS_MAX, PHASE_NOTE_REF_TYPES, PHASE_NOTE_STATUSES, PHASE_NOTE_SUMMARY_MAX, PHASE_NOTE_TYPES, PHASE_NOTE_TYPES_CONVERTIBLE_TO_TASK } from "../phase-journal/phase-journal-constants.js";
import { projectPhaseNote, projectPhaseNoteTaskSuggestion } from "../phase-journal/phase-journal-projections.js";
import { filterOutPassiveExpiredActiveNotes, validatePhaseNoteExpiresAtForWrite } from "../phase-journal/phase-journal-expiry.js";
import { createPhaseJournalStore, phaseNoteTaskSuggestionsTableExists, upsertPhaseNoteTaskSuggestionFromNote } from "../phase-journal/phase-journal-store.js";
import { sortPhaseNotesForContext } from "../phase-journal/phase-journal-scoring.js";
import { inferPhaseKeyFromTask, resolvePhaseKeyForPhaseJournalRead } from "../phase-journal/phase-journal-phase-key.js";
import { rejectIfPhaseNoteTextContainsSecret } from "../phase-journal/phase-journal-secret-guard.js";
import { readPhaseJournalKitPolicy } from "../phase-journal/phase-journal-kit-config.js";
import { newPhaseNoteId, publishPhaseJournalPlanningEvents } from "../phase-journal/phase-journal-planning-events-runtime.js";
import { runConvertPhaseNoteToTaskCommand } from "./phase-journal-convert-command.js";
import { readWorkspaceStatusSnapshotFromDual } from "../persistence/workspace-status-store.js";
function readKitUserVersion(db) {
    const raw = db.pragma("user_version", { simple: true });
    return typeof raw === "number" ? raw : Number(raw);
}
function phaseJournalVersionError(current) {
    return {
        ok: false,
        code: "phase-journal-kit-version",
        message: `Phase journal commands require kit SQLite user_version >= ${PHASE_JOURNAL_MIN_KIT_USER_VERSION} (current ${current}); open the workspace DB once with a current workspace-kit to migrate.`
    };
}
function denyCriticalPhaseNoteWithoutPolicy(commandName) {
    const instructionPath = commandName === "supersede-phase-note"
        ? "src/modules/task-engine/instructions/supersede-phase-note.md"
        : "src/modules/task-engine/instructions/dismiss-phase-note.md";
    return {
        ok: false,
        code: "phase-note-critical-policy-approval-required",
        message: "This active critical phase note cannot be dismissed or superseded without JSON policyApproval while kit.phaseJournal.requirePolicyApprovalForCriticalDismissSupersede is true. Pass policyApproval:{\"confirmed\":true,\"rationale\":\"…\"} on the same run invocation.",
        data: {
            remediation: {
                docPath: CLI_REMEDIATION_DOCS.policyApproval,
                instructionPath
            },
            configKey: "kit.phaseJournal.requirePolicyApprovalForCriticalDismissSupersede"
        }
    };
}
function readStringField(obj, key) {
    const v = obj[key];
    return typeof v === "string" ? v : undefined;
}
function resolveJournalReadPhaseKeyForArgs(cmdArgs, ctx, planning) {
    const taskId = readStringField(cmdArgs, "taskId");
    const phaseKeyIn = readStringField(cmdArgs, "phaseKey");
    const task = taskId ? planning.taskStore.getTask(taskId) : undefined;
    const workspaceStatus = readWorkspaceStatusSnapshotFromDual(planning.sqliteDual);
    const r = resolvePhaseKeyForPhaseJournalRead({
        phaseKey: phaseKeyIn,
        taskId,
        task,
        effectiveConfig: ctx.effectiveConfig,
        workspaceStatus
    });
    if (!r.ok) {
        return { ok: false, code: r.code, message: r.message };
    }
    return { ok: true, phaseKey: r.phaseKey, phaseKeySource: r.source };
}
function parseRefInput(raw) {
    if (!raw || typeof raw !== "object") {
        return null;
    }
    const o = raw;
    const type = typeof o.type === "string" ? o.type.trim() : "";
    const value = typeof o.value === "string" ? o.value.trim() : "";
    if (!type || !value) {
        return null;
    }
    return { type, value };
}
export async function resolvePhaseJournalCommands(command, ctx, planning, store) {
    const names = new Set([
        "add-phase-note",
        "convert-phase-note-to-task",
        "list-phase-notes",
        "get-phase-context",
        "propose-tasks-from-phase-notes",
        "dismiss-phase-note",
        "supersede-phase-note",
        "update-phase-note"
    ]);
    if (!names.has(command.name)) {
        return null;
    }
    const db = planning.sqliteDual.getDatabase();
    const uv = readKitUserVersion(db);
    if (uv < PHASE_JOURNAL_MIN_KIT_USER_VERSION) {
        return phaseJournalVersionError(uv);
    }
    const journalStore = createPhaseJournalStore(db);
    const args = (command.args ?? {});
    const gen = planning.sqliteDual.getPlanningGeneration();
    const draftCtxBase = (commandName, phaseKey) => ({
        commandName,
        moduleId: "task-engine",
        phaseKey
    });
    const suggestionRowsForNote = (noteId) => {
        if (!phaseNoteTaskSuggestionsTableExists(db)) {
            return [];
        }
        return db
            .prepare(`SELECT id, note_id FROM phase_note_task_suggestions WHERE note_id = ?`)
            .all(noteId);
    };
    const okData = (data, code, message) => {
        attachPolicyMeta(data, ctx, gen);
        return { ok: true, code, message, data };
    };
    if (command.name === "add-phase-note") {
        const taskId = readStringField(args, "taskId");
        let phaseKey = readStringField(args, "phaseKey");
        if (!phaseKey && taskId) {
            phaseKey = inferPhaseKeyFromTask(planning.taskStore.getTask(taskId)) ?? undefined;
        }
        if (!phaseKey?.trim()) {
            return { ok: false, code: "invalid-phase-note-args", message: "Provide phaseKey or a taskId whose task has phaseKey/phase metadata." };
        }
        phaseKey = phaseKey.trim();
        if (taskId) {
            const task = planning.taskStore.getTask(taskId);
            if (!task) {
                return { ok: false, code: "phase-note-task-not-found", message: `Unknown taskId '${taskId}'.` };
            }
            const inferred = inferPhaseKeyFromTask(task);
            if (inferred && inferred !== phaseKey) {
                return {
                    ok: false,
                    code: "phase-note-phase-task-mismatch",
                    message: `phaseKey '${phaseKey}' does not match task ${taskId} phase context (${inferred ?? "unknown"}).`
                };
            }
        }
        const noteType = readStringField(args, "noteType")?.trim() ?? "";
        if (!PHASE_NOTE_TYPES.has(noteType)) {
            return {
                ok: false,
                code: "invalid-phase-note-type",
                message: `noteType must be one of: ${[...PHASE_NOTE_TYPES].join(", ")}`
            };
        }
        const summary = readStringField(args, "summary") ?? "";
        if (!summary.trim()) {
            return { ok: false, code: "invalid-phase-note-args", message: "summary is required." };
        }
        if (summary.length > PHASE_NOTE_SUMMARY_MAX) {
            return {
                ok: false,
                code: "invalid-phase-note-args",
                message: `summary exceeds ${PHASE_NOTE_SUMMARY_MAX} characters`
            };
        }
        const detailsRaw = readStringField(args, "details");
        if (detailsRaw && detailsRaw.length > PHASE_NOTE_DETAILS_MAX) {
            return {
                ok: false,
                code: "invalid-phase-note-args",
                message: `details exceeds ${PHASE_NOTE_DETAILS_MAX} characters`
            };
        }
        const secretSummary = rejectIfPhaseNoteTextContainsSecret(summary.trim(), "summary");
        if (!secretSummary.ok) {
            return { ok: false, code: secretSummary.code, message: secretSummary.message };
        }
        if (detailsRaw) {
            const secretDetails = rejectIfPhaseNoteTextContainsSecret(detailsRaw, "details");
            if (!secretDetails.ok) {
                return { ok: false, code: secretDetails.code, message: secretDetails.message };
            }
        }
        const refsRaw = args.refs;
        const refsIn = [];
        if (refsRaw !== undefined) {
            if (!Array.isArray(refsRaw)) {
                return { ok: false, code: "invalid-phase-note-args", message: "refs must be an array when provided." };
            }
            if (refsRaw.length > PHASE_NOTE_REFS_MAX) {
                return {
                    ok: false,
                    code: "invalid-phase-note-args",
                    message: `refs may include at most ${PHASE_NOTE_REFS_MAX} entries`
                };
            }
            for (const r of refsRaw) {
                const parsed = parseRefInput(r);
                if (!parsed) {
                    return { ok: false, code: "invalid-phase-note-args", message: "Each ref needs non-empty type and value strings." };
                }
                if (!PHASE_NOTE_REF_TYPES.has(parsed.type)) {
                    return {
                        ok: false,
                        code: "invalid-phase-note-ref-type",
                        message: `ref type '${parsed.type}' is not allowed for MVP phase notes.`
                    };
                }
                const secretRef = rejectIfPhaseNoteTextContainsSecret(parsed.value, "ref value");
                if (!secretRef.ok) {
                    return { ok: false, code: secretRef.code, message: secretRef.message };
                }
                refsIn.push({ refType: parsed.type, refValue: parsed.value });
            }
        }
        const priorityRaw = readStringField(args, "priority")?.trim() ?? "normal";
        if (!PHASE_NOTE_PRIORITIES.has(priorityRaw)) {
            return { ok: false, code: "invalid-phase-note-priority", message: `priority must be one of: ${[...PHASE_NOTE_PRIORITIES].join(", ")}` };
        }
        const idempotencyKey = readStringField(args, "idempotencyKey")?.trim() || null;
        const expiresAtRaw = readStringField(args, "expiresAt")?.trim() ?? null;
        const expiryGate = validatePhaseNoteExpiresAtForWrite(expiresAtRaw, Date.now());
        if (!expiryGate.ok) {
            return { ok: false, code: "invalid-phase-note-args", message: expiryGate.message };
        }
        const input = {
            phaseKey,
            phaseLabel: readStringField(args, "phaseLabel")?.trim() ?? null,
            taskId: taskId ?? null,
            author: readStringField(args, "author")?.trim() ?? null,
            authorKind: readStringField(args, "authorKind")?.trim() ?? null,
            sessionId: readStringField(args, "sessionId")?.trim() ?? null,
            sourceCommand: "add-phase-note",
            planningGeneration: gen,
            policyTraceId: readStringField(args, "policyTraceId")?.trim() ?? null,
            noteType,
            summary: summary.trim(),
            details: detailsRaw?.trim() ? detailsRaw : null,
            priority: priorityRaw,
            expiresAt: expiresAtRaw,
            idempotencyKey,
            refs: refsIn
        };
        try {
            if (isPlanningGitSyncPublishActive(ctx, "phase_notes")) {
                if (idempotencyKey) {
                    const hit = db
                        .prepare(`SELECT id FROM phase_notes WHERE idempotency_key = ?`)
                        .get(idempotencyKey);
                    if (hit) {
                        const existing = journalStore.getById(hit.id);
                        if (existing) {
                            return okData({ created: false, note: projectPhaseNote(existing) }, "phase-note-created", "Phase note returned (idempotent)");
                        }
                    }
                }
                const noteId = newPhaseNoteId();
                const now = new Date().toISOString();
                const noteSnapshot = phaseNoteRowToEventSnapshot({
                    id: noteId,
                    phaseKey: input.phaseKey,
                    phaseLabel: input.phaseLabel ?? null,
                    taskId: input.taskId ?? null,
                    author: input.author ?? null,
                    authorKind: input.authorKind ?? null,
                    sessionId: input.sessionId ?? null,
                    sourceCommand: input.sourceCommand ?? null,
                    planningGeneration: input.planningGeneration ?? null,
                    policyTraceId: input.policyTraceId ?? null,
                    noteType: input.noteType,
                    summary: input.summary,
                    details: input.details ?? null,
                    status: input.status ?? "active",
                    priority: input.priority ?? "normal",
                    createdAt: now,
                    updatedAt: now,
                    expiresAt: input.expiresAt ?? null,
                    supersededBy: null,
                    convertedTaskId: null,
                    idempotencyKey: input.idempotencyKey ?? null,
                    refs: (input.refs ?? []).map((r, idx) => ({
                        id: `${noteId}-ref-${idx}`,
                        noteId,
                        refType: r.refType,
                        refValue: r.refValue
                    }))
                });
                const clientMutationId = idempotencyKey ?? readIdempotencyValue(args);
                const publishErr = await publishPhaseJournalPlanningEvents({
                    ctx,
                    store,
                    planning,
                    events: [
                        draftPlanningPhaseNoteCreatedEvent({
                            note: noteSnapshot,
                            ctx: { ...draftCtxBase("add-phase-note", phaseKey), clientMutationId }
                        })
                    ],
                    policyApproval: args.policyApproval
                });
                if (publishErr) {
                    return publishErr;
                }
                const created = journalStore.getById(noteId);
                if (!created) {
                    return { ok: false, code: "storage-read-error", message: "Phase note missing after canonical publish." };
                }
                return okData({ created: true, note: projectPhaseNote(created) }, "phase-note-created", "Phase note created");
            }
            const result = journalStore.createNoteIdempotent(input);
            const data = {
                created: result.created,
                note: projectPhaseNote(result.note)
            };
            return okData(data, "phase-note-created", result.created ? "Phase note created" : "Phase note returned (idempotent)");
        }
        catch (err) {
            const msg = err.message;
            if (msg.includes("SQLITE_CONSTRAINT") || msg.includes("UNIQUE")) {
                return {
                    ok: false,
                    code: "phase-note-idempotency-conflict",
                    message: "idempotency_key collides with a different note payload; use a fresh key."
                };
            }
            throw err;
        }
    }
    if (command.name === "list-phase-notes") {
        const taskId = readStringField(args, "taskId");
        const pr = resolveJournalReadPhaseKeyForArgs(args, ctx, planning);
        if (!pr.ok) {
            return pr;
        }
        const phaseKey = pr.phaseKey;
        const phaseKeySource = pr.phaseKeySource;
        let statusFilter;
        if (args.status === undefined) {
            statusFilter = "active";
        }
        else if (typeof args.status === "string") {
            if (!PHASE_NOTE_STATUSES.has(args.status)) {
                return { ok: false, code: "invalid-phase-note-status", message: `Invalid status '${args.status}'.` };
            }
            statusFilter = args.status;
        }
        else if (Array.isArray(args.status)) {
            const st = [];
            for (const s of args.status) {
                if (typeof s !== "string" || !PHASE_NOTE_STATUSES.has(s)) {
                    return { ok: false, code: "invalid-phase-note-status", message: "status array entries must be valid phase note statuses." };
                }
                st.push(s);
            }
            statusFilter = st;
        }
        else {
            return { ok: false, code: "invalid-phase-note-args", message: "status must be a string or string[] when provided." };
        }
        const limitRaw = args.limit;
        let limit = PHASE_NOTE_LIST_DEFAULT_LIMIT;
        if (typeof limitRaw === "number" && Number.isFinite(limitRaw)) {
            limit = Math.min(Math.max(limitRaw, 1), PHASE_NOTE_LIST_MAX_LIMIT);
        }
        let rows = journalStore.listNotes({ phaseKey, status: statusFilter, limit: PHASE_NOTE_LIST_MAX_LIMIT });
        const includeExpired = args.includeExpired === true;
        rows = filterOutPassiveExpiredActiveNotes(rows, includeExpired, Date.now());
        const noteTypeFilter = readStringField(args, "noteType")?.trim();
        if (noteTypeFilter) {
            if (!PHASE_NOTE_TYPES.has(noteTypeFilter)) {
                return { ok: false, code: "invalid-phase-note-type", message: `Invalid noteType filter '${noteTypeFilter}'.` };
            }
            rows = rows.filter((r) => r.noteType === noteTypeFilter);
        }
        if (taskId) {
            rows = rows.filter((r) => r.taskId === taskId);
        }
        rows = rows.slice(0, limit);
        const projections = rows.map(projectPhaseNote);
        return okData({ phaseKey, phaseKeySource, notes: projections, count: projections.length }, "phase-notes-listed", "Listed phase notes");
    }
    if (command.name === "get-phase-context") {
        const taskId = readStringField(args, "taskId");
        const pr = resolveJournalReadPhaseKeyForArgs(args, ctx, planning);
        if (!pr.ok) {
            return pr;
        }
        const phaseKey = pr.phaseKey;
        const phaseKeySource = pr.phaseKeySource;
        const limitRaw = args.limit;
        let limit = PHASE_NOTE_LIST_DEFAULT_LIMIT;
        if (typeof limitRaw === "number" && Number.isFinite(limitRaw)) {
            limit = Math.min(Math.max(limitRaw, 1), PHASE_NOTE_LIST_MAX_LIMIT);
        }
        const refKeys = new Set();
        if (Array.isArray(args.refs)) {
            for (const r of args.refs) {
                const p = parseRefInput(r);
                if (p) {
                    refKeys.add(`${p.type}:${p.value}`);
                }
            }
        }
        const includeExpired = args.includeExpired === true;
        const pool = filterOutPassiveExpiredActiveNotes(journalStore.listNotes({ phaseKey, status: "active", limit: 200 }), includeExpired, Date.now());
        const sorted = sortPhaseNotesForContext(pool, { phaseKey, taskId, refKeys });
        const picked = sorted.slice(0, limit).map(projectPhaseNote);
        return okData({ phaseKey, phaseKeySource, taskId: taskId ?? null, notes: picked, count: picked.length }, "phase-context", "Resolved phase context");
    }
    if (command.name === "propose-tasks-from-phase-notes") {
        const taskId = readStringField(args, "taskId");
        const pr = resolveJournalReadPhaseKeyForArgs(args, ctx, planning);
        if (!pr.ok) {
            return pr;
        }
        const phaseKey = pr.phaseKey;
        const phaseKeySource = pr.phaseKeySource;
        const limitRaw = args.limit;
        let limit = PHASE_NOTE_LIST_DEFAULT_LIMIT;
        if (typeof limitRaw === "number" && Number.isFinite(limitRaw)) {
            limit = Math.min(Math.max(limitRaw, 1), PHASE_NOTE_LIST_MAX_LIMIT);
        }
        const pool = filterOutPassiveExpiredActiveNotes(journalStore.listNotes({ phaseKey, status: "active", limit: PHASE_NOTE_LIST_MAX_LIMIT }), false, Date.now());
        const convertible = pool
            .filter((n) => PHASE_NOTE_TYPES_CONVERTIBLE_TO_TASK.has(n.noteType))
            .filter((n) => (taskId ? n.taskId === taskId : true))
            .slice(0, limit);
        const proposals = convertible.map(projectPhaseNote);
        const persist = args.persist === true;
        if (!persist) {
            return okData({
                phaseKey,
                phaseKeySource,
                taskId: taskId ?? null,
                proposals,
                persistedSuggestions: [],
                persisted: false,
                count: proposals.length
            }, "phase-note-proposals-listed", "Listed convertible phase notes as proposals (read-only; no task writes)");
        }
        if (uv < PHASE_NOTE_TASK_SUGGESTIONS_MIN_KIT_USER_VERSION) {
            return {
                ok: false,
                code: "phase-note-suggestions-kit-version",
                message: `persist requires kit SQLite user_version >= ${PHASE_NOTE_TASK_SUGGESTIONS_MIN_KIT_USER_VERSION} (current ${uv}).`
            };
        }
        if (!phaseNoteTaskSuggestionsTableExists(db)) {
            return {
                ok: false,
                code: "phase-note-suggestions-table-missing",
                message: "phase_note_task_suggestions table is missing; reopen planning SQLite with a current workspace-kit to migrate."
            };
        }
        if (isPlanningGitSyncPublishActive(ctx, "phase_note_suggestions")) {
            const baseClientMutationId = readIdempotencyValue(args);
            const events = [];
            for (const n of convertible) {
                const existingRow = db
                    .prepare(`SELECT * FROM phase_note_task_suggestions WHERE note_id = ?`)
                    .get(n.id);
                const now = new Date().toISOString();
                const title = n.summary.trim();
                const description = (n.details?.trim() ? n.details.trim() : n.summary.trim()) || title;
                const suggestionSnapshot = {
                    id: existingRow ? String(existingRow.id) : newPhaseNoteId(),
                    noteId: n.id,
                    title,
                    description,
                    suggestedStatus: "proposed",
                    suggestedPhaseKey: n.phaseKey,
                    suggestedPhaseLabel: n.phaseLabel ?? null,
                    suggestedTaskType: "workspace-kit",
                    acceptanceCriteriaJson: null,
                    convertedTaskId: null,
                    createdAt: existingRow ? String(existingRow.created_at) : now,
                    updatedAt: now
                };
                const noteMutationId = baseClientMutationId ? `${baseClientMutationId}::${n.id}` : undefined;
                const ctxDraft = { ...draftCtxBase("propose-tasks-from-phase-notes", phaseKey), clientMutationId: noteMutationId };
                if (existingRow) {
                    events.push(draftPlanningPhaseNoteSuggestionUpdatedEvent({ suggestion: suggestionSnapshot, ctx: ctxDraft }));
                }
                else {
                    events.push(draftPlanningPhaseNoteSuggestionCreatedEvent({ suggestion: suggestionSnapshot, ctx: ctxDraft }));
                }
            }
            const publishErr = await publishPhaseJournalPlanningEvents({
                ctx,
                store,
                planning,
                events,
                policyApproval: args.policyApproval
            });
            if (publishErr) {
                return publishErr;
            }
            const persistedSuggestions = convertible.map((n) => {
                const row = db
                    .prepare(`SELECT * FROM phase_note_task_suggestions WHERE note_id = ?`)
                    .get(n.id);
                if (!row) {
                    throw new Error(`phase journal: suggestion missing after canonical persist for ${n.id}`);
                }
                return projectPhaseNoteTaskSuggestion({
                    id: String(row.id),
                    noteId: String(row.note_id),
                    title: String(row.title),
                    description: String(row.description),
                    suggestedStatus: String(row.suggested_status),
                    suggestedPhaseKey: String(row.suggested_phase_key),
                    suggestedPhaseLabel: row.suggested_phase_label == null ? null : String(row.suggested_phase_label),
                    suggestedTaskType: row.suggested_task_type == null ? null : String(row.suggested_task_type),
                    acceptanceCriteriaJson: row.acceptance_criteria_json == null ? null : String(row.acceptance_criteria_json),
                    convertedTaskId: row.converted_task_id == null ? null : String(row.converted_task_id),
                    createdAt: String(row.created_at),
                    updatedAt: String(row.updated_at)
                });
            });
            return okData({
                phaseKey,
                phaseKeySource,
                taskId: taskId ?? null,
                proposals,
                persistedSuggestions,
                persisted: true,
                count: proposals.length
            }, "phase-note-proposals-persisted", "Upserted phase_note_task_suggestions rows for convertible notes");
        }
        const persistedSuggestions = convertible.map((n) => projectPhaseNoteTaskSuggestion(upsertPhaseNoteTaskSuggestionFromNote(db, n)));
        return okData({
            phaseKey,
            phaseKeySource,
            taskId: taskId ?? null,
            proposals,
            persistedSuggestions,
            persisted: true,
            count: proposals.length
        }, "phase-note-proposals-persisted", "Upserted phase_note_task_suggestions rows for convertible notes");
    }
    if (command.name === "convert-phase-note-to-task") {
        return await runConvertPhaseNoteToTaskCommand(ctx, planning, store, args);
    }
    if (command.name === "update-phase-note") {
        const noteId = readStringField(args, "noteId")?.trim() ?? "";
        if (!noteId) {
            return { ok: false, code: "invalid-phase-note-args", message: "noteId is required." };
        }
        const nowMs = Date.now();
        const patch = {};
        if (typeof args.summary === "string") {
            const s = args.summary.trim();
            if (!s) {
                return { ok: false, code: "invalid-phase-note-args", message: "summary, when provided, must be non-empty." };
            }
            if (s.length > PHASE_NOTE_SUMMARY_MAX) {
                return {
                    ok: false,
                    code: "invalid-phase-note-args",
                    message: `summary exceeds ${PHASE_NOTE_SUMMARY_MAX} characters`
                };
            }
            patch.summary = s;
        }
        if ("details" in args) {
            if (args.details !== null && typeof args.details !== "string") {
                return { ok: false, code: "invalid-phase-note-args", message: "details must be a string or null." };
            }
            const d = args.details === null ? null : String(args.details).trim();
            if (d && d.length > PHASE_NOTE_DETAILS_MAX) {
                return {
                    ok: false,
                    code: "invalid-phase-note-args",
                    message: `details exceeds ${PHASE_NOTE_DETAILS_MAX} characters`
                };
            }
            patch.details = d;
        }
        if ("expiresAt" in args) {
            const rawExp = args.expiresAt;
            if (rawExp !== null && typeof rawExp !== "string") {
                return { ok: false, code: "invalid-phase-note-args", message: "expiresAt must be a string or null." };
            }
            const expFin = rawExp === null ? null : String(rawExp).trim() || null;
            if (expFin) {
                const v = validatePhaseNoteExpiresAtForWrite(expFin, nowMs);
                if (!v.ok) {
                    return { ok: false, code: "invalid-phase-note-args", message: v.message };
                }
            }
            patch.expiresAt = expFin;
        }
        if (Object.keys(patch).length === 0) {
            return {
                ok: false,
                code: "invalid-phase-note-args",
                message: "Provide at least one of: summary, details, expiresAt."
            };
        }
        if (patch.summary !== undefined) {
            const gs = rejectIfPhaseNoteTextContainsSecret(patch.summary, "summary");
            if (!gs.ok) {
                return { ok: false, code: gs.code, message: gs.message };
            }
        }
        if (patch.details !== undefined && patch.details !== null) {
            const gd = rejectIfPhaseNoteTextContainsSecret(patch.details, "details");
            if (!gd.ok) {
                return { ok: false, code: gd.code, message: gd.message };
            }
        }
        const existing = journalStore.getById(noteId);
        if (!existing) {
            return { ok: false, code: "phase-note-not-found", message: `Unknown noteId '${noteId}'.` };
        }
        if (existing.status !== "active") {
            return { ok: false, code: "phase-note-not-updatable", message: "Only active phase notes can be updated." };
        }
        const updatedAt = new Date().toISOString();
        if (isPlanningGitSyncPublishActive(ctx, "phase_notes")) {
            const nextNote = {
                ...existing,
                summary: patch.summary !== undefined ? patch.summary : existing.summary,
                details: patch.details !== undefined ? patch.details : existing.details,
                expiresAt: patch.expiresAt !== undefined ? patch.expiresAt : existing.expiresAt,
                planningGeneration: gen,
                updatedAt
            };
            const publishErr = await publishPhaseJournalPlanningEvents({
                ctx,
                store,
                planning,
                events: [
                    draftPlanningPhaseNoteUpdatedEvent({
                        note: phaseNoteRowToEventSnapshot(nextNote),
                        ctx: {
                            ...draftCtxBase("update-phase-note", existing.phaseKey),
                            clientMutationId: readIdempotencyValue(args)
                        }
                    })
                ],
                policyApproval: args.policyApproval
            });
            if (publishErr) {
                return publishErr;
            }
            const updated = journalStore.getById(noteId);
            if (!updated) {
                return {
                    ok: false,
                    code: "phase-note-not-found",
                    message: `Unknown noteId '${noteId}' or note not active.`
                };
            }
            return okData({ note: projectPhaseNote(updated) }, "phase-note-updated", "Updated phase note");
        }
        const updated = journalStore.updateActivePhaseNote(noteId, patch, {
            planningGeneration: gen,
            updatedAt
        });
        if (!updated) {
            return {
                ok: false,
                code: "phase-note-not-found",
                message: `Unknown noteId '${noteId}' or note not active.`
            };
        }
        return okData({ note: projectPhaseNote(updated) }, "phase-note-updated", "Updated phase note");
    }
    if (command.name === "dismiss-phase-note") {
        const noteId = readStringField(args, "noteId")?.trim() ?? "";
        if (!noteId) {
            return { ok: false, code: "invalid-phase-note-args", message: "noteId is required." };
        }
        const reason = readStringField(args, "reason")?.trim() ?? "";
        if (!reason) {
            return { ok: false, code: "invalid-phase-note-args", message: "reason is required (audit discipline); not persisted in MVP schema." };
        }
        if (reason.length > 2000) {
            return { ok: false, code: "invalid-phase-note-args", message: "reason is too long." };
        }
        const secretReason = rejectIfPhaseNoteTextContainsSecret(reason, "dismiss reason");
        if (!secretReason.ok) {
            return { ok: false, code: secretReason.code, message: secretReason.message };
        }
        const existing = journalStore.getById(noteId);
        if (!existing) {
            return { ok: false, code: "phase-note-not-found", message: `Unknown noteId '${noteId}'.` };
        }
        const phaseJournalPolicy = readPhaseJournalKitPolicy(ctx.effectiveConfig);
        if (phaseJournalPolicy.requirePolicyApprovalForCriticalDismissSupersede &&
            existing.status === "active" &&
            existing.priority === "critical" &&
            !parsePolicyApproval(args)) {
            return denyCriticalPhaseNoteWithoutPolicy("dismiss-phase-note");
        }
        if (isPlanningGitSyncPublishActive(ctx, "phase_notes") ||
            isPlanningGitSyncPublishActive(ctx, "phase_note_suggestions")) {
            const updatedAt = new Date().toISOString();
            const archived = phaseNoteRowToEventSnapshot({ ...existing, status: "dismissed", updatedAt });
            const baseMutationId = readIdempotencyValue(args);
            const events = [
                draftPlanningPhaseNoteArchivedEvent({
                    note: archived,
                    ctx: {
                        ...draftCtxBase("dismiss-phase-note", existing.phaseKey),
                        clientMutationId: baseMutationId
                    }
                })
            ];
            for (const s of suggestionRowsForNote(noteId)) {
                events.push(draftPlanningPhaseNoteSuggestionRemovedEvent({
                    suggestionId: s.id,
                    noteId,
                    ctx: {
                        ...draftCtxBase("dismiss-phase-note", existing.phaseKey),
                        clientMutationId: baseMutationId ? `${baseMutationId}::suggestion::${s.id}` : undefined
                    }
                }));
            }
            const publishErr = await publishPhaseJournalPlanningEvents({
                ctx,
                store,
                planning,
                events,
                policyApproval: args.policyApproval
            });
            if (publishErr) {
                return publishErr;
            }
        }
        else {
            journalStore.dismissNote(noteId);
        }
        const updated = journalStore.getById(noteId);
        if (!updated) {
            return { ok: false, code: "phase-note-not-found", message: `Note '${noteId}' missing after dismiss.` };
        }
        return okData({ note: projectPhaseNote(updated), reasonAcknowledged: true }, "phase-note-dismissed", "Phase note dismissed");
    }
    if (command.name === "supersede-phase-note") {
        const noteId = readStringField(args, "noteId")?.trim() ?? "";
        const supersededBy = readStringField(args, "supersededBy")?.trim() ?? "";
        if (!noteId || !supersededBy) {
            return { ok: false, code: "invalid-phase-note-args", message: "noteId and supersededBy are required." };
        }
        if (noteId === supersededBy) {
            return { ok: false, code: "invalid-phase-note-args", message: "noteId and supersededBy must differ." };
        }
        const from = journalStore.getById(noteId);
        const to = journalStore.getById(supersededBy);
        if (!from || !to) {
            return { ok: false, code: "phase-note-not-found", message: "noteId and supersededBy must both exist." };
        }
        if (from.phaseKey !== to.phaseKey) {
            return {
                ok: false,
                code: "invalid-phase-note-args",
                message: "supersede requires both notes in the same phase_key."
            };
        }
        if (to.status !== "active") {
            return { ok: false, code: "invalid-phase-note-args", message: "supersededBy note must be active." };
        }
        const nowMs = Date.now();
        if (filterOutPassiveExpiredActiveNotes([to], false, nowMs).length === 0) {
            return {
                ok: false,
                code: "invalid-phase-note-args",
                message: "supersededBy note must not be past expires_at while still active; extend expiry or pick another note."
            };
        }
        const phaseJournalPolicy = readPhaseJournalKitPolicy(ctx.effectiveConfig);
        if (phaseJournalPolicy.requirePolicyApprovalForCriticalDismissSupersede &&
            from.status === "active" &&
            from.priority === "critical" &&
            !parsePolicyApproval(args)) {
            return denyCriticalPhaseNoteWithoutPolicy("supersede-phase-note");
        }
        if (isPlanningGitSyncPublishActive(ctx, "phase_notes") ||
            isPlanningGitSyncPublishActive(ctx, "phase_note_suggestions")) {
            const updatedAt = new Date().toISOString();
            const archived = phaseNoteRowToEventSnapshot({
                ...from,
                status: "superseded",
                supersededBy,
                updatedAt
            });
            const baseMutationId = readIdempotencyValue(args);
            const events = [
                draftPlanningPhaseNoteArchivedEvent({
                    note: archived,
                    ctx: {
                        ...draftCtxBase("supersede-phase-note", from.phaseKey),
                        clientMutationId: baseMutationId
                    }
                })
            ];
            for (const s of suggestionRowsForNote(noteId)) {
                events.push(draftPlanningPhaseNoteSuggestionRemovedEvent({
                    suggestionId: s.id,
                    noteId,
                    ctx: {
                        ...draftCtxBase("supersede-phase-note", from.phaseKey),
                        clientMutationId: baseMutationId ? `${baseMutationId}::suggestion::${s.id}` : undefined
                    }
                }));
            }
            const publishErr = await publishPhaseJournalPlanningEvents({
                ctx,
                store,
                planning,
                events,
                policyApproval: args.policyApproval
            });
            if (publishErr) {
                return publishErr;
            }
        }
        else {
            journalStore.supersedeNote(noteId, supersededBy);
        }
        const updated = journalStore.getById(noteId);
        if (!updated) {
            return { ok: false, code: "phase-note-not-found", message: `Note '${noteId}' missing after supersede.` };
        }
        return okData({ note: projectPhaseNote(updated) }, "phase-note-superseded", "Phase note superseded");
    }
    return null;
}
