import {
  parseReviewQueueItem,
  projectReviewQueue
} from "../chunk-ZRSUCTST.js";
import {
  isWorkProductStatus,
  parseAgentCheckInput,
  parseArtifactInput,
  parseEvidenceInput,
  parseExceptionInput,
  persistedPartToWorkProduct,
  unresolvedBlockingExceptions,
  workProductToPersistedPart
} from "../chunk-V3HO43PG.js";
import {
  ToolInputError,
  defineAppTool
} from "../chunk-YEFFHORB.js";

// src/work-product/service.ts
var WORK_PRODUCT_TRANSITIONS = {
  draft: /* @__PURE__ */ new Set(["blocked", "ready"]),
  blocked: /* @__PURE__ */ new Set(["draft"]),
  ready: /* @__PURE__ */ new Set(["changes_requested", "approved", "superseded"]),
  changes_requested: /* @__PURE__ */ new Set(["draft", "superseded"]),
  approved: /* @__PURE__ */ new Set(["superseded"]),
  superseded: /* @__PURE__ */ new Set()
};
function canTransitionWorkProduct(from, to) {
  return WORK_PRODUCT_TRANSITIONS[from].has(to);
}
function isWorkProductTerminal(status) {
  return WORK_PRODUCT_TRANSITIONS[status].size === 0;
}
function rejected(error) {
  return { succeeded: false, error, conflict: false };
}
function lostRace(id) {
  return { succeeded: false, error: `Work product ${id} changed concurrently`, conflict: true };
}
function mergeById(existing, incoming) {
  const merged = existing.slice();
  const indexById = new Map(merged.map((entry, index) => [entry.id, index]));
  for (const entry of incoming) {
    const at = indexById.get(entry.id);
    if (at === void 0) {
      indexById.set(entry.id, merged.length);
      merged.push(entry);
    } else {
      merged[at] = entry;
    }
  }
  return merged;
}
function createWorkProductService(options) {
  const { store } = options;
  const now = options.now ?? (() => Date.now());
  const generateId = options.generateId ?? (() => crypto.randomUUID());
  async function appendEvent(record, step, message, metadata = {}) {
    await store.appendEvent({
      workProductId: record.id,
      workspaceId: record.workspaceId,
      step,
      message,
      metadata,
      at: now()
    });
  }
  async function transition(id, to, patch = {}, eventMeta = {}) {
    const record = await store.load(id);
    if (!record) return rejected(`Work product ${id} not found`);
    const from = record.status;
    if (isWorkProductTerminal(from)) {
      return rejected(`Work product ${id} is terminal (${from}); cannot transition to ${to}`);
    }
    if (!canTransitionWorkProduct(from, to)) {
      return rejected(`Illegal work-product transition ${from} -> ${to} for ${id}`);
    }
    const updated = await store.update(
      id,
      { status: from, version: record.version },
      { status: to, updatedAt: now(), ...patch }
    );
    if (!updated) return lostRace(id);
    await appendEvent(updated, `wp.${to}`, `Work product ${from} -> ${to}`, { from, to, ...eventMeta });
    return { succeeded: true, value: updated };
  }
  async function guardedMerge(id, legalStatuses, build, event) {
    const record = await store.load(id);
    if (!record) return rejected(`Work product ${id} not found`);
    if (!legalStatuses.includes(record.status)) {
      return rejected(`Work product ${id} is ${record.status}; expected ${legalStatuses.join("/")}`);
    }
    const updated = await store.update(
      id,
      { status: record.status, version: record.version },
      { updatedAt: now(), ...build(record) }
    );
    if (!updated) return lostRace(id);
    await appendEvent(updated, event.step, event.message(updated), event.metadata?.(updated) ?? {});
    return { succeeded: true, value: updated };
  }
  const create = async (input) => {
    const at = now();
    const record = await store.insert(
      {
        id: input.id ?? generateId(),
        workspaceId: input.workspaceId,
        threadId: input.threadId,
        scopeKey: input.scopeKey,
        status: "draft",
        version: input.version ?? 1,
        artifact: null,
        evidence: [],
        exceptions: [],
        checks: [],
        provenance: input.provenance,
        history: [],
        createdAt: at,
        updatedAt: at
      },
      input.extras
    );
    await appendEvent(record, "wp.created", `Work product draft v${record.version} created for ${record.scopeKey}`, {
      scopeKey: record.scopeKey,
      version: record.version,
      threadId: record.threadId
    });
    return record;
  };
  async function findByScopeAndStatus(workspaceId, scopeKey, status) {
    const rows = await store.listByWorkspace(workspaceId, { status: [status] });
    return rows.find((row) => row.scopeKey === scopeKey) ?? null;
  }
  const nextVersion = async (workspaceId, scopeKey) => {
    const reviewed = await store.listByWorkspace(workspaceId, { status: ["approved", "superseded"] });
    const versions = reviewed.filter((row) => row.scopeKey === scopeKey).map((row) => row.version);
    return versions.length === 0 ? 1 : Math.max(...versions) + 1;
  };
  const reopen = async (id) => {
    const record = await store.load(id);
    if (!record) return rejected(`Work product ${id} not found`);
    if (record.status !== "changes_requested") {
      return rejected(`Work product ${id} is ${record.status}; only changes_requested reopens`);
    }
    const updated = await store.update(
      id,
      { status: "changes_requested", version: record.version },
      { status: "draft", version: record.version + 1, updatedAt: now() }
    );
    if (!updated) return lostRace(id);
    await appendEvent(updated, "wp.reopened", `Correction draft v${updated.version} opened`, {
      from: record.version,
      to: updated.version
    });
    return { succeeded: true, value: updated };
  };
  const upsertEvidence = (id, entries) => guardedMerge(
    id,
    ["draft", "blocked"],
    (record) => ({ evidence: mergeById(record.evidence, entries) }),
    {
      step: "wp.evidence",
      message: (record) => `Evidence upserted (${entries.length} entries, ${record.evidence.length} total)`,
      metadata: () => ({ upserted: entries.map((entry) => entry.id) })
    }
  );
  const upsertExceptions = async (id, entries) => {
    const merged = await guardedMerge(
      id,
      ["draft", "blocked"],
      (record2) => ({ exceptions: mergeById(record2.exceptions, entries) }),
      {
        step: "wp.exception",
        message: (record2) => `Exceptions upserted (${entries.length} entries, ${unresolvedBlockingExceptions(record2.exceptions).length} blocking unresolved)`,
        metadata: () => ({ upserted: entries.map((entry) => entry.id) })
      }
    );
    if (!merged.succeeded) return merged;
    const record = merged.value;
    const blocking = unresolvedBlockingExceptions(record.exceptions).length;
    if (record.status === "draft" && blocking > 0) {
      return transition(id, "blocked", {}, { blocking });
    }
    if (record.status === "blocked" && blocking === 0) {
      return transition(id, "draft", {}, { blocking });
    }
    return merged;
  };
  const recordChecks = (id, checks) => guardedMerge(
    id,
    ["draft", "blocked"],
    () => ({ checks: checks.slice() }),
    {
      step: "wp.checks",
      message: () => `Checks recorded (${checks.length}, ${checks.filter((check) => !check.passed).length} failed)`,
      metadata: () => ({ failed: checks.filter((check) => !check.passed).map((check) => check.name) })
    }
  );
  const submit = async (id, input) => {
    const record = await store.load(id);
    if (!record) return rejected(`Work product ${id} not found`);
    if (record.status !== "draft") {
      return rejected(`Work product ${id} is ${record.status}; only a draft submits`);
    }
    const blocking = unresolvedBlockingExceptions(record.exceptions);
    if (blocking.length > 0) {
      return rejected(
        `Work product ${id} has ${blocking.length} unresolved blocking exception(s): ${blocking.map((entry2) => entry2.id).join(", ")}`
      );
    }
    const artifactPath = input.artifactPath ?? input.artifact.path;
    const entry = {
      version: record.version,
      status: "ready",
      provenance: input.provenance,
      ...artifactPath === void 0 ? {} : { artifactPath },
      at: now()
    };
    return transition(
      id,
      "ready",
      {
        artifact: input.artifact,
        checks: input.checks.slice(),
        provenance: input.provenance,
        history: [...record.history, entry]
      },
      { version: record.version, failedChecks: input.checks.filter((check) => !check.passed).length }
    );
  };
  const applyVerdict = async (id, input) => {
    const record = await store.load(id);
    if (!record) return rejected(`Work product ${id} not found`);
    if (record.status !== "ready") {
      return rejected(`Work product ${id} is ${record.status}; a verdict applies only to ready`);
    }
    const to = input.verdict === "approve" ? "approved" : "changes_requested";
    const entry = {
      version: record.version,
      status: to,
      provenance: record.provenance,
      ...record.artifact?.path === void 0 ? {} : { artifactPath: record.artifact.path },
      reviewedBy: input.reviewedBy,
      ...input.note === void 0 ? {} : { reviewNote: input.note },
      at: now()
    };
    const outcome = await transition(
      id,
      to,
      { history: [...record.history, entry] },
      { verdict: input.verdict, reviewedBy: input.reviewedBy }
    );
    if (!outcome.succeeded || to !== "approved") return outcome;
    const priorApproved = await store.listByWorkspace(record.workspaceId, { status: ["approved"] });
    for (const prior of priorApproved) {
      if (prior.id === id || prior.scopeKey !== record.scopeKey) continue;
      await transition(prior.id, "superseded", {}, { supersededBy: id });
    }
    return outcome;
  };
  return {
    create,
    get: (id) => store.load(id),
    openDraft: (workspaceId, scopeKey) => store.findDraft(workspaceId, scopeKey),
    awaitingCorrection: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, "changes_requested"),
    awaitingReview: (workspaceId, scopeKey) => findByScopeAndStatus(workspaceId, scopeKey, "ready"),
    nextVersion,
    reopen,
    upsertEvidence,
    upsertExceptions,
    recordChecks,
    submit,
    applyVerdict,
    supersede: (id) => transition(id, "superseded")
  };
}
function createInMemoryWorkProductStore() {
  const rows = /* @__PURE__ */ new Map();
  const events = [];
  return {
    async load(id) {
      const record = rows.get(id);
      return record ? structuredClone(record) : null;
    },
    async findDraft(workspaceId, scopeKey) {
      for (const record of rows.values()) {
        if (record.workspaceId === workspaceId && record.scopeKey === scopeKey && (record.status === "draft" || record.status === "blocked")) {
          return structuredClone(record);
        }
      }
      return null;
    },
    async listByWorkspace(workspaceId, opts) {
      const out = [];
      for (const record of rows.values()) {
        if (record.workspaceId !== workspaceId) continue;
        if (opts?.status && !opts.status.includes(record.status)) continue;
        out.push(structuredClone(record));
      }
      return out;
    },
    async insert(record) {
      if (rows.has(record.id)) throw new Error(`Work product ${record.id} already exists`);
      rows.set(record.id, structuredClone(record));
      return structuredClone(record);
    },
    async update(id, guard, patch) {
      const current = rows.get(id);
      if (!current) return null;
      if (guard.status !== void 0 && current.status !== guard.status) return null;
      if (guard.version !== void 0 && current.version !== guard.version) return null;
      const next = { ...current };
      if (patch.status !== void 0) next.status = patch.status;
      if (patch.version !== void 0) next.version = patch.version;
      if (patch.artifact !== void 0) next.artifact = patch.artifact;
      if (patch.evidence !== void 0) next.evidence = patch.evidence;
      if (patch.exceptions !== void 0) next.exceptions = patch.exceptions;
      if (patch.checks !== void 0) next.checks = patch.checks;
      if (patch.provenance !== void 0) next.provenance = patch.provenance;
      if (patch.history !== void 0) next.history = patch.history;
      if (patch.updatedAt !== void 0) next.updatedAt = patch.updatedAt;
      rows.set(id, structuredClone(next));
      return structuredClone(next);
    },
    async appendEvent(event) {
      events.push(structuredClone(event));
    },
    events() {
      return events.map((event) => structuredClone(event));
    },
    put(record) {
      rows.set(record.id, structuredClone(record));
    }
  };
}

// src/work-product/provenance.ts
function stampProvenance(base, now = Date.now) {
  return { ...base, servingModels: [], producedAt: now() };
}
async function finalizeWorkProductProvenance(store, input) {
  const rows = await store.listByWorkspace(input.workspaceId);
  const updated = [];
  for (const record of rows) {
    const recordMatches = record.provenance.runId === input.runId;
    const historyMatches = record.history.some((entry) => entry.provenance.runId === input.runId);
    if (!recordMatches && !historyMatches) continue;
    const finalize = (provenance) => ({
      ...provenance,
      servingModels: [...input.servingModels],
      ...input.costUsd === void 0 ? {} : { costUsd: input.costUsd }
    });
    const next = await store.update(
      record.id,
      { status: record.status, version: record.version },
      {
        ...recordMatches ? { provenance: finalize(record.provenance) } : {},
        history: record.history.map(
          (entry) => entry.provenance.runId === input.runId ? { ...entry, provenance: finalize(entry.provenance) } : entry
        )
      }
    );
    if (!next) {
      input.logger?.warn(`[work-product] provenance back-fill lost a race on ${record.id}; skipped`);
      continue;
    }
    await store.appendEvent({
      workProductId: record.id,
      workspaceId: record.workspaceId,
      step: "wp.provenance",
      message: `Serving models back-filled for run ${input.runId}`,
      metadata: { runId: input.runId, servingModels: [...input.servingModels], costUsd: input.costUsd ?? null },
      at: Date.now()
    });
    updated.push(next);
  }
  return updated;
}
function workProductTrustInputs(records, verdictsFor) {
  const items = [];
  for (const record of records) {
    const verdicts = verdictsFor(record);
    if (!verdicts || verdicts.length === 0) continue;
    items.push({ itemId: record.id, verdicts });
  }
  return items;
}

// src/work-product/quote.ts
var WHITESPACE = /[\s\p{Zs}\u2028\u2029\u200b-\u200d\ufeff]+/gu;
var DASHES = /[\u2010-\u2015\u2212\ufe58\ufe63\uff0d]/gu;
var SINGLE_QUOTES = /[\u2018\u2019\u201a\u201b\u2032\u2035\u00b4`]/gu;
var DOUBLE_QUOTES = /[\u201c\u201d\u201e\u201f\u2033\u2036\u00ab\u00bb]/gu;
function normalizeQuoteText(value) {
  return value.normalize("NFKC").replace(DASHES, "-").replace(SINGLE_QUOTES, "'").replace(DOUBLE_QUOTES, '"').replace(WHITESPACE, " ").trim();
}
function sourceContainsQuote(sourceText, quote) {
  const trimmed = quote.trim();
  if (trimmed.length === 0) return false;
  if (sourceText.includes(trimmed)) return true;
  const normalizedQuote = normalizeQuoteText(quote);
  if (normalizedQuote.length === 0) return false;
  return normalizeQuoteText(sourceText).includes(normalizedQuote);
}
function sliceSourceSpan(sourceText, span) {
  for (const field of ["start", "end"]) {
    const value = span[field];
    if (!Number.isInteger(value)) return { ok: false, failure: { reason: "not_integer", field } };
    if (value < 0) return { ok: false, failure: { reason: "negative", field } };
  }
  if (span.end <= span.start) return { ok: false, failure: { reason: "inverted" } };
  if (span.end > sourceText.length) {
    return { ok: false, failure: { reason: "out_of_range", totalChars: sourceText.length } };
  }
  const quote = sourceText.slice(span.start, span.end);
  if (quote.trim().length === 0) return { ok: false, failure: { reason: "blank" } };
  return { ok: true, quote };
}

// src/work-product/tools.ts
var MAX_WORK_PRODUCT_BATCH = 50;
var EVIDENCE_COVERAGE_CHECK = "evidence_coverage";
var QUOTE_VERIFICATION_CHECK = "quote_verification";
async function unwrap(run, code) {
  let outcome = await run();
  if (!outcome.succeeded && outcome.conflict) outcome = await run();
  if (!outcome.succeeded) throw new ToolInputError(code, outcome.error, outcome.conflict ? 409 : 400);
  return outcome.value;
}
function requireScopeKey(args) {
  const scopeKey = typeof args.scopeKey === "string" ? args.scopeKey.trim() : "";
  if (!scopeKey) throw new ToolInputError("missing_scope_key", "scopeKey is required \u2014 the engagement key this work product belongs to.");
  return scopeKey;
}
function requireBatch(args, field) {
  const raw = args[field];
  if (!Array.isArray(raw) || raw.length === 0) {
    throw new ToolInputError("missing_entries", `${field} must be a non-empty array.`);
  }
  if (raw.length > MAX_WORK_PRODUCT_BATCH) {
    throw new ToolInputError("batch_too_large", `${field} accepts at most ${MAX_WORK_PRODUCT_BATCH} entries per call \u2014 send smaller batches.`);
  }
  return raw;
}
async function resolveDraft(service, config, scopeKey, ctx) {
  const open = await service.openDraft(ctx.workspaceId, scopeKey);
  if (open) return open;
  const awaitingReview = await service.awaitingReview(ctx.workspaceId, scopeKey);
  if (awaitingReview) {
    throw new ToolInputError(
      "awaiting_review",
      `Work product for ${scopeKey} (v${awaitingReview.version}) is awaiting review \u2014 no further emission until a reviewer verdict.`,
      409
    );
  }
  const awaitingCorrection = await service.awaitingCorrection(ctx.workspaceId, scopeKey);
  if (awaitingCorrection) {
    return unwrap(() => service.reopen(awaitingCorrection.id), "reopen_failed");
  }
  return service.create({
    workspaceId: ctx.workspaceId,
    threadId: ctx.threadId,
    scopeKey,
    version: await service.nextVersion(ctx.workspaceId, scopeKey),
    provenance: stampProvenance(config.provenance(ctx), config.now)
  });
}
function spanErrorDetail(failure) {
  switch (failure.reason) {
    case "not_integer":
      return `${failure.field} must be a whole character offset.`;
    case "negative":
      return `${failure.field} must not be negative.`;
    case "inverted":
      return "end must be greater than start \u2014 the range is half-open [start, end).";
    case "out_of_range":
      return `end is past the end of the document, which is ${failure.totalChars} characters. Offsets are absolute in the whole document: when you read with an offset, add that offset to the position within the returned text.`;
    case "blank":
      return "that range is only whitespace. Widen it to the characters that carry the value.";
  }
}
async function resolveEvidenceQuotes(config, entries, ctx) {
  const readSourceText = config.readSourceText;
  const texts = /* @__PURE__ */ new Map();
  const readText = async (ref) => {
    if (!texts.has(ref)) texts.set(ref, await readSourceText(ref, ctx));
    return texts.get(ref) ?? null;
  };
  for (let index = 0; index < entries.length; index += 1) {
    const entry = entries[index];
    const span = entry.locator.span;
    const quote = entry.locator.quote;
    if (span) {
      if (!readSourceText) {
        throw new ToolInputError(
          "span_unsupported",
          `entries[${index}].locator.span: this deployment cannot read source text, so a span cannot be resolved into a quote. Record the entry without locator.span.`,
          500
        );
      }
      const text2 = await readText(entry.sourceRef);
      if (text2 === null) {
        throw new ToolInputError(
          "unverifiable_quote",
          `entries[${index}].locator.span: "${entry.sourceRef}" has no readable text, so the span cannot be resolved. Record the entry without locator.span or locator.quote and state the basis in claim.`
        );
      }
      const sliced = sliceSourceSpan(text2, span);
      if (!sliced.ok) {
        throw new ToolInputError(
          "invalid_span",
          `entries[${index}].locator.span [${span.start}, ${span.end}) into "${entry.sourceRef}": ${spanErrorDetail(sliced.failure)}`
        );
      }
      entry.locator.quote = sliced.quote;
      entry.locator.quoteBasis = "span";
      continue;
    }
    if (quote === void 0 || quote.trim().length === 0) continue;
    if (!readSourceText) continue;
    const text = await readText(entry.sourceRef);
    if (text === null) {
      throw new ToolInputError(
        "unverifiable_quote",
        `entries[${index}].locator.quote: "${entry.sourceRef}" has no readable text, so the quote cannot be verified. Record the entry without locator.quote and state the basis in claim.`
      );
    }
    if (!sourceContainsQuote(text, quote)) {
      throw new ToolInputError(
        "quote_not_found",
        `entries[${index}].locator.quote: ${JSON.stringify(quote)} does not occur in "${entry.sourceRef}". Cite locator.span {start, end} instead \u2014 the character range you read it at, which this platform slices out of the document itself so the text is right by construction. If the value is COMPUTED rather than read, omit both and state the computation in claim.`
      );
    }
    entry.locator.quoteBasis = "model";
  }
}
async function summarizeQuoteVerification(config, evidence, ctx) {
  const readSourceText = config.readSourceText;
  if (!readSourceText) return void 0;
  const texts = /* @__PURE__ */ new Map();
  let verified = 0;
  let spanAnchored = 0;
  let withoutQuote = 0;
  const failed = [];
  for (const entry of evidence) {
    const quote = entry.locator.quote;
    if (quote === void 0 || quote.trim().length === 0) {
      withoutQuote += 1;
      continue;
    }
    if (!texts.has(entry.sourceRef)) texts.set(entry.sourceRef, await readSourceText(entry.sourceRef, ctx));
    const text = texts.get(entry.sourceRef);
    if (typeof text !== "string") {
      failed.push(entry.id);
      continue;
    }
    const span = entry.locator.span;
    if (span) {
      const sliced = sliceSourceSpan(text, span);
      if (sliced.ok && sliced.quote === quote) {
        verified += 1;
        spanAnchored += 1;
      } else failed.push(entry.id);
      continue;
    }
    if (sourceContainsQuote(text, quote)) verified += 1;
    else failed.push(entry.id);
  }
  return { verified, spanAnchored, withoutQuote, failed };
}
function buildWorkProductTools(config) {
  const service = createWorkProductService({
    store: config.store,
    ...config.now ? { now: config.now } : {},
    ...config.generateId ? { generateId: config.generateId } : {}
  });
  const upsertEvidence = defineAppTool({
    name: "upsert_evidence",
    description: "Record source\u2192field lineage for the current work product, incrementally as you find it. Each entry links a source document (sourceRef + locator) to one artifact target and states the claim it supports. Cite by locator.span {start, end} \u2014 the character range you read the value at \u2014 and the platform slices the supporting quote out of the document for you. Re-emitting an entry id replaces that entry. Address the work product by scopeKey; the first call creates the draft.",
    parameters: {
      type: "object",
      properties: {
        scopeKey: { type: "string", description: "Engagement key for this work product." },
        entries: {
          type: "array",
          minItems: 1,
          maxItems: MAX_WORK_PRODUCT_BATCH,
          items: {
            type: "object",
            properties: {
              id: { type: "string", description: "Stable entry id \u2014 re-emit to replace." },
              sourceRef: { type: "string", description: "Vault path / attachment id of the SOURCE document." },
              locator: {
                type: "object",
                properties: {
                  page: { type: "number" },
                  range: { type: "string", description: "Free-form location: 'L120-L134' | 'B7' | '\xB64'." },
                  span: {
                    type: "object",
                    description: "PREFERRED. The character range you read the value at, as absolute offsets into the whole document: start is the first character, end is one past the last. If you read with an offset, add that offset to the position within the text you got back. The platform slices the quote out of the document itself and stores it, and returns it to you \u2014 you never retype source text, so the citation cannot be wrong. Use this whenever the value was READ from a document.",
                    properties: {
                      start: { type: "integer", minimum: 0 },
                      end: { type: "integer", minimum: 1 }
                    },
                    required: ["start", "end"]
                  },
                  quote: {
                    type: "string",
                    description: "Fallback for sources that cannot give you character offsets: the supporting text copied character-for-character. The platform checks it occurs in the document and REFUSES the entry if it does not. Prefer span. For a value you COMPUTED rather than read, omit both and state the computation in claim."
                  }
                }
              },
              target: { type: "string", description: "Artifact field/claim this evidence supports." },
              claim: { type: "string", description: "The value/assertion at the target." },
              confidence: { type: "number", minimum: 0, maximum: 1 }
            },
            required: ["id", "sourceRef", "target", "claim"]
          }
        }
      },
      required: ["scopeKey", "entries"]
    },
    async execute(args, ctx) {
      const scopeKey = requireScopeKey(args);
      const raw = requireBatch(args, "entries");
      const entries = [];
      for (let index = 0; index < raw.length; index += 1) {
        const parsed = parseEvidenceInput(raw[index], `entries[${index}]`);
        if (!parsed.ok) throw new ToolInputError("invalid_evidence", `${parsed.field}: ${parsed.error}`);
        entries.push(parsed.value);
      }
      for (let index = 0; index < entries.length; index += 1) {
        const entry = entries[index];
        if (!await config.resolveSourceRef(entry.sourceRef, ctx)) {
          throw new ToolInputError(
            "unknown_source_ref",
            `entries[${index}].sourceRef: "${entry.sourceRef}" does not resolve to an existing source document.`
          );
        }
      }
      await resolveEvidenceQuotes(config, entries, ctx);
      const draft = await resolveDraft(service, config, scopeKey, ctx);
      const record = await unwrap(() => service.upsertEvidence(draft.id, entries), "evidence_rejected");
      return {
        workProductId: record.id,
        version: record.version,
        evidenceCount: record.evidence.length,
        // Echo what was actually STORED for the entries in this call. A span
        // citation's quote is produced here, not sent here, so the model must
        // be able to see the text its offsets selected — that is how it
        // notices an off-by-a-line span without a second read.
        entries: entries.map((entry) => ({
          id: entry.id,
          target: entry.target,
          ...entry.locator.quote === void 0 ? {} : { quote: entry.locator.quote },
          ...entry.locator.quoteBasis === void 0 ? {} : { quoteBasis: entry.locator.quoteBasis }
        }))
      };
    }
  });
  const flagException = defineAppTool({
    name: "flag_exception",
    description: "Flag problems with the current work product (missing documents, inconsistent sources, \u2026). An unresolved blocking exception parks the work product until it is resolved; re-emit the same id with resolved:true to release it. Address by scopeKey.",
    parameters: {
      type: "object",
      properties: {
        scopeKey: { type: "string", description: "Engagement key for this work product." },
        exceptions: {
          type: "array",
          minItems: 1,
          maxItems: MAX_WORK_PRODUCT_BATCH,
          items: {
            type: "object",
            properties: {
              id: { type: "string", description: "Stable entry id \u2014 re-emit to replace." },
              severity: { type: "string", enum: ["blocking", "material", "advisory"] },
              kind: { type: "string", description: "Exception kind from the product vocabulary." },
              message: { type: "string" },
              targets: { type: "array", items: { type: "string" } },
              resolved: { type: "boolean" },
              resolutionNote: { type: "string" }
            },
            required: ["id", "severity", "kind", "message"]
          }
        }
      },
      required: ["scopeKey", "exceptions"]
    },
    async execute(args, ctx) {
      const scopeKey = requireScopeKey(args);
      const raw = requireBatch(args, "exceptions");
      const entries = [];
      for (let index = 0; index < raw.length; index += 1) {
        const parsed = parseExceptionInput(raw[index], `exceptions[${index}]`);
        if (!parsed.ok) throw new ToolInputError("invalid_exception", `${parsed.field}: ${parsed.error}`);
        if (!config.exceptionKinds.includes(parsed.value.kind)) {
          throw new ToolInputError(
            "invalid_exception",
            `exceptions[${index}].kind: must be one of: ${config.exceptionKinds.join(", ")}`
          );
        }
        if (parsed.value.resolved && parsed.value.resolvedBy === void 0) parsed.value.resolvedBy = "agent";
        entries.push(parsed.value);
      }
      const draft = await resolveDraft(service, config, scopeKey, ctx);
      const record = await unwrap(() => service.upsertExceptions(draft.id, entries), "exception_rejected");
      return {
        workProductId: record.id,
        version: record.version,
        status: record.status,
        unresolvedBlocking: unresolvedBlockingExceptions(record.exceptions).length
      };
    }
  });
  const submitWorkProduct = defineAppTool({
    name: "submit_work_product",
    description: "Submit the finished work product for professional review \u2014 the terminal call after evidence and exceptions are recorded. Refused while a blocking exception is unresolved, or when a material target lacks evidence. Include your own quality checks in `checks`.",
    parameters: {
      type: "object",
      properties: {
        scopeKey: { type: "string", description: "Engagement key for this work product." },
        artifact: {
          type: "object",
          properties: {
            kind: { type: "string", description: "Artifact kind from the product vocabulary." },
            title: { type: "string" },
            path: { type: "string", description: "Vault/object-store ref of the rendered document." },
            content: { type: "string", description: "Inline body when small (markdown/JSON)." },
            mediaType: { type: "string" },
            baseline: {
              type: "object",
              properties: { path: { type: "string" }, content: { type: "string" } },
              description: "For diff-first artifacts: the source document being redlined."
            },
            fields: { type: "object", description: "Structured field map lineage targets anchor to." }
          },
          required: ["kind", "title"]
        },
        checks: {
          type: "array",
          items: {
            type: "object",
            properties: {
              id: { type: "string" },
              name: { type: "string" },
              passed: { type: "boolean" },
              detail: { type: "string" }
            },
            required: ["id", "name", "passed"]
          },
          description: "Your own quality self-checks (recorded as agent-sourced)."
        }
      },
      required: ["scopeKey", "artifact"]
    },
    async execute(args, ctx) {
      const scopeKey = requireScopeKey(args);
      const parsedArtifact = parseArtifactInput(args.artifact);
      if (!parsedArtifact.ok) throw new ToolInputError("invalid_artifact", `${parsedArtifact.field}: ${parsedArtifact.error}`);
      const artifact = parsedArtifact.value;
      if (!config.artifactKinds.includes(artifact.kind)) {
        throw new ToolInputError("invalid_artifact", `artifact.kind: must be one of: ${config.artifactKinds.join(", ")}`);
      }
      const agentChecks = [];
      if (args.checks !== void 0) {
        if (!Array.isArray(args.checks)) throw new ToolInputError("invalid_checks", "checks must be an array when present.");
        for (let index = 0; index < args.checks.length; index += 1) {
          const parsed = parseAgentCheckInput(args.checks[index], `checks[${index}]`);
          if (!parsed.ok) throw new ToolInputError("invalid_checks", `${parsed.field}: ${parsed.error}`);
          agentChecks.push({ ...parsed.value, source: "agent" });
        }
      }
      const draft = await resolveDraft(service, config, scopeKey, ctx);
      const blocking = unresolvedBlockingExceptions(draft.exceptions);
      if (blocking.length > 0) {
        throw new ToolInputError(
          "blocking_exceptions_unresolved",
          `Cannot submit: ${blocking.length} unresolved blocking exception(s) (${blocking.map((entry) => entry.id).join(", ")}). Resolve each via flag_exception with resolved:true, or downgrade its severity with a resolutionNote justifying why it does not block.`,
          409
        );
      }
      const checks = [...agentChecks];
      const quotes = await summarizeQuoteVerification(config, draft.evidence, ctx);
      if (quotes) {
        const quoted = quotes.verified + quotes.failed.length;
        checks.unshift({
          id: QUOTE_VERIFICATION_CHECK,
          name: QUOTE_VERIFICATION_CHECK,
          passed: quotes.failed.length === 0,
          detail: quotes.failed.length === 0 ? `${quotes.verified}/${quoted} quoted evidence entries verified against their source (${quotes.spanAnchored} platform-sliced from a source span, ${quotes.verified - quotes.spanAnchored} model-quoted and proved to occur); ${quotes.withoutQuote} recorded without a quote` : `Unverifiable quotes on: ${quotes.failed.join(", ")}`,
          source: "platform"
        });
        if (quotes.failed.length > 0) {
          await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
          throw new ToolInputError(
            "quote_verification_failed",
            `Cannot submit: ${quotes.failed.length} evidence entr${quotes.failed.length === 1 ? "y quotes" : "ies quote"} text that does not occur in the source named (${quotes.failed.join(", ")}). Re-emit each with a quote copied character-for-character from that document, or without locator.quote if the value is computed.`
          );
        }
      }
      if (config.materialTargets) {
        const targets = config.materialTargets(artifact);
        const covered = new Set(draft.evidence.map((entry) => entry.target));
        const missing = targets.filter((target) => !covered.has(target));
        const anchoredTargets = new Set(
          draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map((entry) => entry.target)
        );
        const spanTargets = new Set(
          draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map((entry) => entry.target)
        );
        const present = targets.filter((target) => covered.has(target));
        const unanchored = present.filter((target) => !anchoredTargets.has(target));
        const spanCount = present.filter((target) => spanTargets.has(target)).length;
        const breakdown = `${spanCount} span-anchored, ${present.length - spanCount - unanchored.length} quote-verified, ${unanchored.length} claim-only`;
        const coverage = {
          id: EVIDENCE_COVERAGE_CHECK,
          name: EVIDENCE_COVERAGE_CHECK,
          passed: missing.length === 0 && !(config.requireAnchoredEvidence && unanchored.length > 0),
          detail: missing.length > 0 ? `Missing evidence for: ${missing.join(", ")}` : config.requireAnchoredEvidence && unanchored.length > 0 ? `No source anchor for: ${unanchored.join(", ")}` : `${targets.length}/${targets.length} material targets evidenced (${breakdown})`,
          source: "platform"
        };
        checks.unshift(coverage);
        if (missing.length > 0) {
          await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
          throw new ToolInputError(
            "evidence_coverage_failed",
            `Cannot submit: material targets lack evidence: ${missing.join(", ")}. Add upsert_evidence entries targeting each, then resubmit.`
          );
        }
        if (config.requireAnchoredEvidence && unanchored.length > 0) {
          await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
          throw new ToolInputError(
            "evidence_not_anchored",
            `Cannot submit: these material targets have an evidence row but no source anchor: ${unanchored.join(", ")}. Re-emit each with locator.span {start, end} \u2014 the character range in the document you read the value at. The platform slices the quote out of the document itself, so you never retype source text.`
          );
        }
      }
      const provenance = stampProvenance(config.provenance(ctx), config.now);
      const record = await unwrap(
        () => service.submit(draft.id, { artifact, checks, provenance }),
        "submit_rejected"
      );
      await config.onReady?.(record, ctx);
      return {
        workProductId: record.id,
        version: record.version,
        status: record.status,
        checks: record.checks.map((check) => ({ name: check.name, passed: check.passed, source: check.source }))
      };
    }
  });
  return [upsertEvidence, flagException, submitWorkProduct];
}

// src/work-product/route.ts
function validateWorkProductVerdictBody(body) {
  const id = typeof body.id === "string" && body.id.trim() ? body.id.trim() : null;
  if (!id) return { ok: false, error: "Missing work product id" };
  const verdict = body.verdict;
  if (verdict !== "approve" && verdict !== "request_changes") {
    return { ok: false, error: "Invalid verdict: expected approve or request_changes" };
  }
  const note = body.note === void 0 ? void 0 : typeof body.note === "string" ? body.note.trim() : null;
  if (note === null) return { ok: false, error: "Invalid note: expected a string" };
  if (verdict === "request_changes") {
    if (!note) return { ok: false, error: "request_changes requires a note \u2014 it becomes the correction instruction in chat" };
    return { ok: true, id, verdict, note };
  }
  return note ? { ok: true, id, verdict, note } : { ok: true, id, verdict };
}
function createWorkProductRoutes(options) {
  const logger = options.logger ?? console;
  const service = createWorkProductService({
    store: options.store,
    ...options.now ? { now: options.now } : {}
  });
  async function list(request) {
    const auth = await options.authorize({ request, intent: "list" });
    if (!auth.ok) return auth.response;
    const url = new URL(request.url);
    const statusParam = url.searchParams.get("status");
    const statuses = statusParam ? statusParam.split(",").map((value) => value.trim()).filter(Boolean) : null;
    const workProducts = await options.store.listByWorkspace(
      auth.workspaceId,
      statuses ? { status: statuses } : void 0
    );
    return Response.json({ workProducts });
  }
  async function detail(request, id) {
    const auth = await options.authorize({ request, intent: "detail" });
    if (!auth.ok) return auth.response;
    const record = await options.store.load(id);
    if (!record || record.workspaceId !== auth.workspaceId) {
      return Response.json({ error: "Work product not found" }, { status: 404 });
    }
    return Response.json({ workProduct: record });
  }
  async function verdict(request) {
    const body = await request.json().catch(() => null);
    if (!body || typeof body !== "object" || Array.isArray(body)) {
      return Response.json({ error: "Invalid JSON body" }, { status: 400 });
    }
    const validation = validateWorkProductVerdictBody(body);
    if (!validation.ok) return Response.json({ error: validation.error }, { status: 400 });
    const auth = await options.authorize({ request, intent: "verdict", body });
    if (!auth.ok) return auth.response;
    const existing = await options.store.load(validation.id);
    if (!existing || existing.workspaceId !== auth.workspaceId) {
      return Response.json({ error: "Work product not found" }, { status: 404 });
    }
    const outcome = await service.applyVerdict(validation.id, {
      verdict: validation.verdict,
      reviewedBy: auth.reviewedBy,
      ...validation.note === void 0 ? {} : { note: validation.note }
    });
    if (!outcome.succeeded) {
      return Response.json({ code: "VERDICT_CONFLICT", error: outcome.error }, { status: 409 });
    }
    const record = outcome.value;
    try {
      await options.persistAnchorPart?.(workProductToPersistedPart(record), record);
    } catch (error) {
      logger.error("[work-product] persistAnchorPart failed:", error);
    }
    try {
      await options.onVerdict?.({
        record,
        verdict: validation.verdict,
        ...validation.note === void 0 ? {} : { note: validation.note },
        reviewedBy: auth.reviewedBy
      });
    } catch (error) {
      logger.error("[work-product] onVerdict failed:", error);
    }
    if (validation.verdict === "approve") {
      try {
        await options.onExport?.(record);
      } catch (error) {
        logger.error("[work-product] onExport failed:", error);
      }
    }
    return Response.json({ ok: true, workProduct: record });
  }
  return { list, detail, verdict };
}
export {
  EVIDENCE_COVERAGE_CHECK,
  MAX_WORK_PRODUCT_BATCH,
  QUOTE_VERIFICATION_CHECK,
  buildWorkProductTools,
  canTransitionWorkProduct,
  createInMemoryWorkProductStore,
  createWorkProductRoutes,
  createWorkProductService,
  finalizeWorkProductProvenance,
  isWorkProductStatus,
  isWorkProductTerminal,
  normalizeQuoteText,
  parseAgentCheckInput,
  parseArtifactInput,
  parseEvidenceInput,
  parseExceptionInput,
  parseReviewQueueItem,
  persistedPartToWorkProduct,
  projectReviewQueue,
  sliceSourceSpan,
  sourceContainsQuote,
  stampProvenance,
  unresolvedBlockingExceptions,
  validateWorkProductVerdictBody,
  workProductToPersistedPart,
  workProductTrustInputs
};
//# sourceMappingURL=index.js.map