← Files DataARCHIVED FILE

skills/visualize-data/scripts/inline-sources-input.mjs

11.5 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

import {
  assertDisclosedSourceText,
  assertJson,
  MAX_INLINE_INPUT_BYTES,
  MAX_INLINE_ROWS,
  sourceForInput,
} from "./inline-chart-input.mjs";
import { receiptSourceEntries, reviewedSource, safeSourceHref } from "../../../templates/data-app/base/src/source-provenance.js";

export const MAX_RECEIPT_ITEMS = 20;
const idPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
const privateField = /(?:^|_)(?:e_*mail|phone|ssn|password|passwd|secret|token|authorization|api_key|credit_card|card_number|account_number)(?:_|$)/iu;

function fail(message) {
  throw new Error(`Invalid inline sources: ${message}`);
}

function record(value, name) {
  if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${name} must be an object.`);
  return value;
}

function text(value, name, max = 2_000) {
  if (typeof value !== "string" || !value.trim() || value.length > max)
    fail(`${name} must be nonempty text of at most ${max} characters.`);
  return value.trim();
}

function identifier(value, name) {
  const id = text(value, name, 100);
  if (!idPattern.test(id)) fail(`${name} must be a lowercase hyphenated identifier.`);
  return id;
}

function timestamp(value, name) {
  const result = text(value, name, 100);
  const match = /^(\d{4})-(\d\d)-(\d\d)T.*(?:Z|[+-]\d\d:\d\d)$/u.exec(result);
  const [year, month, day] = match?.slice(1).map(Number) ?? [];
  const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
  const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
  if (!match || !Number.isFinite(Date.parse(result)) || !(day >= 1 && day <= daysInMonth))
    fail(`${name} must be a recorded ISO timestamp with a valid calendar date and its timezone.`);
  return result;
}

function methodSourceText({ language, code }) {
  if (language !== "python") return code;
  // Scan exact string literals and comments for unsafe URLs, but do not treat
  // Python's unquoted floor-division operator as a protocol-relative URL.
  // This is only a validation view; the recorded code is never rewritten.
  return code.replace(/'''(?:\\[\s\S]|(?!''')[^\\])*'''|"""(?:\\[\s\S]|(?!""")[^\\])*"""|'(?:\\[\s\S]|[^'\\\r\n])*'|"(?:\\[\s\S]|[^"\\\r\n])*"|#[^\r\n]*|(?:\b[a-z][a-z\d+.-]*:)?\/\//giu,
    (token) => token === "//" ? " / " : token);
}

// Only inspect the projected receipt, never excluded tool-output extensions.
// This supplements field projection; it does not certify arbitrary text as safe.
function assertSafeEvidence(value) {
  if (typeof value === "string") {
    if (/\b(?:Bearer\s+[a-z\d._-]{12,}|sk-[a-z\d_-]{20,})|-----BEGIN [A-Z ]*PRIVATE KEY-----/iu.test(value)
      || /\b(?:password|passwd|api[_-]?key|access[_-]?token|secret)\s*[=:]\s*["'][^"']+["']/iu.test(value)
      || /\b[a-z][a-z\d+.-]*:\/\/[^\s/]+:[^\s/]+@/iu.test(value))
      fail("projected evidence contains credentials; provide a reviewed, redacted source.");
  } else if (Array.isArray(value)) value.forEach(assertSafeEvidence);
  else if (value && typeof value === "object") Object.values(value).forEach(assertSafeEvidence);
}

function methodsForInput(value) {
  if (value === undefined) return [];
  if (!Array.isArray(value) || value.length > 2) fail("methods must contain at most Python and calculation records.");
  const languages = new Set();
  return value.map((method) => {
    record(method, "method");
    if (!["python", "calculation"].includes(method.language) || languages.has(method.language))
      fail("each recorded method must have a unique python or calculation language.");
    languages.add(method.language);
    text(method.code, "method.code", 100_000);
    // Preserve exact recorded code, including whitespace; never execute it.
    return { language: method.language, code: method.code };
  });
}

function sourceRolesForInput(value, source) {
  if (value === undefined) return undefined;
  if (!Array.isArray(value) || value.length > 40) fail("sourceRoles must be an array of at most 40 source roles.");
  const identities = receiptSourceEntries(reviewedSource(source));
  const seen = new Set();
  return value.map((entry) => {
    record(entry, "sourceRole");
    const label = text(entry.label, "sourceRole.label", 500);
    const role = text(entry.role, "sourceRole.role", 1_000);
    const href = entry.href === undefined ? undefined : safeSourceHref(entry.href);
    if (entry.href !== undefined && !href) fail("sourceRole.href must be a safe source URL.");
    const matching = identities.filter((identity) => identity.kind === entry.kind && identity.label === label
      && (href === undefined || identity.href === href));
    if (matching.length !== 1) fail("each sourceRole must identify exactly one source in its own query.");
    const identity = matching[0];
    const key = JSON.stringify([identity.kind, identity.label, identity.href ?? null]);
    if (seen.has(key)) fail("sourceRoles must not repeat a source identity within a query.");
    seen.add(key);
    return { kind: identity.kind, label: identity.label, ...(identity.href ? { href: identity.href } : {}), role };
  });
}

function assumptionsForInput(value) {
  if (value === undefined) return undefined;
  if (!Array.isArray(value) || value.length > 20)
    fail("item.assumptions must be an array of at most 20 material assumptions.");
  return [...new Set(value.map((entry) => text(entry, "item.assumptions entry")))];
}

function previewForInput(query, rows) {
  if (query.preview === undefined) return undefined;
  const preview = record(query.preview, "preview");
  if (rows === undefined) fail("preview metadata requires recorded rows.");
  if (!["sample", "partial", "aggregate"].includes(preview.kind))
    fail("preview.kind must be sample, partial, or aggregate.");
  const note = text(preview.note, "preview.note");
  if (preview.totalRows !== undefined && (!Number.isSafeInteger(preview.totalRows) || preview.totalRows < rows.length))
    fail("preview.totalRows must be a recorded count no smaller than the preview.");
  return { kind: preview.kind, note, ...(preview.totalRows !== undefined ? { totalRows: preview.totalRows } : {}) };
}

function columnsForInput(query) {
  if (query.rows === undefined) {
    if (query.columns !== undefined) fail("columns require recorded rows.");
    return undefined;
  }
  if (!Array.isArray(query.columns) || !query.columns.length || query.columns.length > 80)
    fail("recorded rows require 1–80 explicitly approved preview columns.");
  const columns = query.columns.map((column) => {
    if (typeof column === "string") return { field: text(column, "preview column", 200) };
    record(column, "preview column");
    return { field: text(column.field, "preview column.field", 200),
      ...(column.label !== undefined ? { label: text(column.label, "preview column.label", 200) } : {}) };
  });
  if (new Set(columns.map(({ field }) => field)).size !== columns.length) fail("preview columns must be unique.");
  for (const { field } of columns) {
    const normalized = field.replace(/([a-z\d])([A-Z])/gu, "$1_$2").replace(/[^a-z\d]/giu, "_");
    if (privateField.test(normalized)) fail("preview columns must omit credentials and direct contact/payment identifiers.");
  }
  return columns;
}

function rowsForInput(query, columnDefinitions) {
  if (query.rows === undefined) return undefined;
  if (!Array.isArray(query.rows) || query.rows.length > MAX_INLINE_ROWS)
    fail(`rows must be an array of at most ${MAX_INLINE_ROWS} recorded rows.`);
  const columns = columnDefinitions.map(({ field }) => field);
  const rows = query.rows.map((row) => {
    record(row, "row");
    return Object.fromEntries(columns.filter((field) => Object.hasOwn(row, field)).map((field) => {
      const value = row[field];
      if (value !== null && !["string", "number", "boolean"].includes(typeof value))
        fail("preview cells must be scalar recorded values.");
      return [field, value];
    }));
  });
  if (rows.length && columns.some((field) => !rows.some((row) => Object.hasOwn(row, field))))
    fail("an approved preview column is missing from the recorded rows.");
  return rows;
}

/** A card owns its queries, so shared query IDs cannot leak another finding's scope. */
export function normalizeInlineSourcesInput(input) {
  record(input, "input");
  assertJson(input);
  if (Buffer.byteLength(JSON.stringify(input)) > MAX_INLINE_INPUT_BYTES) fail("input exceeds 2 MB.");
  if (input.schemaVersion !== 1) fail("schemaVersion must be 1.");
  if (!Array.isArray(input.items) || !input.items.length || input.items.length > MAX_RECEIPT_ITEMS)
    fail(`items must contain 1–${MAX_RECEIPT_ITEMS} evidence-backed answer cards.`);
  const itemIds = new Set();
  let totalRows = 0;
  const items = input.items.map((item) => {
    record(item, "item");
    const id = identifier(item.id, "item.id");
    if (itemIds.has(id)) fail("item IDs must be unique.");
    itemIds.add(id);
    const title = text(item.title, "item.title", 500);
    const description = item.description === undefined ? undefined : text(item.description, "item.description");
    if (!Array.isArray(item.queries) || !item.queries.length || item.queries.length > 10)
      fail("each item requires 1–10 recorded queries or source snapshots.");
    const queryIds = new Set();
    const queries = item.queries.map((query) => {
      record(query, "query");
      const queryId = identifier(query.id, "query.id");
      if (queryIds.has(queryId)) fail("query IDs must be unique within each card.");
      queryIds.add(queryId);
      const columns = columnsForInput(query);
      const rows = rowsForInput(query, columns);
      totalRows += rows?.length ?? 0;
      if (totalRows > MAX_INLINE_ROWS) fail(`the complete receipt exceeds ${MAX_INLINE_ROWS} preview rows.`);
      const source = sourceForInput(query.source, { id, title, description }, {
        includeSql: true, includeSourceUrls: true, displayedFields: columns?.map(({ field }) => field) ?? [], scopeToFields: false,
      });
      if (source.executedAt) source.executedAt = timestamp(source.executedAt, "source.executedAt");
      // The receipt must display the exact recorded query, not a trimmed rewrite.
      const rawSql = query.source.sql ?? query.source.query?.sql;
      if (source.sql) source.sql = rawSql;
      const methods = methodsForInput(query.methods);
      const sourceRoles = sourceRolesForInput(query.sourceRoles, source);
      const preview = previewForInput(query, rows);
      return {
        id: queryId, source,
        ...(query.summary !== undefined ? { summary: text(query.summary, "query.summary") } : {}),
        ...(sourceRoles?.length ? { sourceRoles } : {}),
        ...(rows !== undefined ? { rows, columns } : {}),
        ...(methods.length ? { methods } : {}),
        ...(preview ? { preview } : {}),
        ...(query.reportingPeriod !== undefined ? { reportingPeriod: text(query.reportingPeriod, "reportingPeriod", 500) } : {}),
        ...(query.capturedAt !== undefined ? { capturedAt: timestamp(query.capturedAt, "capturedAt") } : {}),
      };
    });
    if (item.confidence !== undefined) fail("item.confidence is not supported in inline Sources receipts.");
    const assumptions = assumptionsForInput(item.assumptions);
    return { id, title, ...(description ? { description } : {}),
      ...(assumptions?.length ? { assumptions } : {}), queries };
  });
  const normalized = {
    schemaVersion: 1, kind: "sources", items,
    theme: identifier(input.theme ?? "codex-classic", "theme"),
  };
  assertDisclosedSourceText({ ...normalized, items: items.map((item) => ({ ...item,
    queries: item.queries.map((query) => ({ ...query,
      methods: query.methods?.map((method) => ({ ...method, code: methodSourceText(method) })),
    })),
  })) }, true);
  assertSafeEvidence(normalized);
  return normalized;
}

SHA-256: 0f24d315409614214a8d1d472974ae2a427319fcddf41e48a2b5132943cfa478