UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

631 lines • 30.6 kB
"use strict"; /** * coverage/period — the ONE period shape a result declares, the ONE rule set * every door asks, and the ONE verdict. * * Pattern: one rule set, every door (the `items.ts` law for `short` and * `kind`): the three mints — `absent()`, `coverage()`, * `describedResult()` — REFUSE a malformed period at the line the * author wrote, and the recognizers READ a period an envelope minted * elsewhere (a Python helper, an older or newer process) without * repairing it. Both ask {@link periodProblem}, so a mint and a read * cannot disagree about what a well-formed period is. * Role: core/ layer leaf of the result doors (honesty layer 3). Imports only * `refusal.ts`, the dev-mode flag and the time layer's one instant * parser (`time/instant.ts` · `instantOf`, lenient profile) and its * person-facing renderer (`time/present.ts`). The results layer * (`../../core/agent/results/subflow.ts`) files one verdict per call from * {@link periodVerdict}; the limits block prints one line per * declaring call from {@link periodLine}. * Emits: N/A. * * ## The shape — anchored on the READ * * ```ts * period: { * queried: { from: '2026-09-26T09:00:00Z', to: '2026-09-26T10:00:00Z' }, // what the read asked for * held: { from: '2026-08-27T02:00:00Z', to: '2026-09-26T02:00:00Z' }, // what the store holds — or 'unknown' * readAt: '2026-09-26T10:00:03Z', // when the read ran (optional) * } * // on the wire: "period": { "queried": {…}, "held": {…} | "unknown", "read_at"? } * ``` * * Every value is an ISO 8601 instant WITH a zone (`Z` or `±HH:MM`) — the * library compares instants, and an instant without a zone is ambiguous, so it * is refused rather than guessed. `held: 'unknown'` is the store saying out * loud that it cannot vouch for what it holds. Nothing here ever parses a * duration: the library compares instants the tool declared and never reads * "2h" (the inputs layer's `ToolPeriod` says which ARGUMENT set the period; * this shape says what the READ covered). * * ## Why not the word `window`, and why not a coverage item * * `window` names the context window (`core/agent/window/`, `.window()`). A * coverage item's `kind` is a closed, record-only word: it cannot hold two * instants to compare. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.clockLines = exports.periodCheckLine = exports.periodRowIsWellFormed = exports.PERIOD_ROW_VERDICTS = exports.periodLine = exports.noteWithClause = exports.noteWithoutClause = exports.unservedPeriod = exports.servedPeriod = exports.PERIOD_VERDICT_CLAUSES = exports.SERVED_VERDICT_KEY = exports.leastHeld = exports.periodVerdict = exports._resetPeriodWarnings = exports.warnDroppedDeclaration = exports.copyPeriod = exports.readPeriod = exports.mintPeriod = exports.periodProblem = exports.PERIOD_WIRE = void 0; const footprintjs_1 = require("footprintjs"); const instant_js_1 = require("../../time/instant.js"); const checkRecord_js_1 = require("../../time/checkRecord.js"); const refusal_js_1 = require("./refusal.js"); /** * The period's wire spelling as DATA — every reserved key and the one literal * (`'unknown'`) — so a tool written in another language mints it byte for * byte. `canonical-notes.json` publishes it, generated from this constant * (`scripts/gen-canonical-notes.mjs`): the JSON cannot disagree with the code. * * @example * ```ts * PERIOD_WIRE.readAt; // 'read_at' — the one key whose wire spelling differs * ``` */ exports.PERIOD_WIRE = Object.freeze({ /** The key a period sits under on `af_absent`, `af_coverage` and `af_semantics`. */ key: 'period', queried: 'queried', held: 'held', from: 'from', to: 'to', readAt: 'read_at', /** The literal `held` carries when the store cannot vouch for what it holds. */ heldUnknown: 'unknown', }); const isPlainObject = (value) => { if (typeof value !== 'object' || value === null || Array.isArray(value)) return false; const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; }; /** The shown form of a value in a message — JSON, clipped: a message is never a copy of a document. */ function shown(value) { let text; try { text = JSON.stringify(value) ?? String(value); } catch { text = String(value); } return text.length > 60 ? `${text.slice(0, 57)}…` : text; } const readAtKey = (s) => (s === 'camel' ? 'readAt' : 'read_at'); /** The first unknown key of `value`, as a problem — naming the spelling meant when it is a slip. */ function unknownKey(value, known, at) { for (const key of Object.keys(value)) { if (known.includes(key)) continue; const meant = (0, refusal_js_1.spellingMeant)(key, known); return { field: `${at}.${key}`, message: `'${at}.${key}' is not a field this vocabulary has` + (meant === undefined ? '.' : ` — did you mean \`${meant}\`?`) + ` The fields of \`${at}\` are: ${known.join(', ')}.`, }; } return undefined; } /** The problem with one instant field, or `undefined` when it names an instant. */ function instantProblem(value, at) { if ((0, instant_js_1.instantOf)(value, 'lenient') !== undefined) return undefined; return { field: at, message: `\`${at}\` must be an ISO 8601 instant with a zone — e.g. '2026-09-26T08:00:00Z' or ` + `'2026-09-26T10:00:00+02:00' — got ${shown(value)}. The library compares instants, and ` + 'one with no zone could be any of 24 hours, so it is refused rather than guessed.', }; } /** The problem with one `{ from, to }` span, or `undefined` when it is one. */ function spanProblem(value, at) { if (!isPlainObject(value)) { return { field: at, message: `\`${at}\` must be { from, to } — two instants, got ${shown(value)}.`, }; } const extra = unknownKey(value, ['from', 'to'], at); if (extra !== undefined) return extra; const bad = instantProblem(value.from, `${at}.from`) ?? instantProblem(value.to, `${at}.to`); if (bad !== undefined) return bad; // Both parse — checked just above. if ((0, instant_js_1.compareInstants)((0, instant_js_1.instantOf)(value.from, 'lenient'), (0, instant_js_1.instantOf)(value.to, 'lenient')) > 0) { return { field: at, message: `\`${at}.from\` (${String(value.from)}) is after \`${at}.to\` (${String(value.to)}) — ` + 'a span runs forward.', }; } return undefined; } /** * THE rule set: the first fault in a period, or `undefined` when it is well * formed — asked by every mint (which refuses it) and every recognizer (which * drops it from the record and serves the envelope as the tool wrote it). * * In order: a plain object; no key it does not have (a spelling slip names the * key meant — `read_at` at a camelCase door names `readAt`); `queried` a * `{ from, to }` span of instants with `from` not after `to`; `held` the * literal `'unknown'` or a span by the same rule; `readAt` an instant. * * @example * ```ts * periodProblem({ queried: { from: 'yesterday', to: '2026-09-26T10:00:00Z' }, held: 'unknown' }, 'camel'); * // → { field: 'period.queried.from', message: '`period.queried.from` must be an ISO 8601 instant with a zone — …' } * ``` */ function periodProblem(value, spelling) { const readAt = readAtKey(spelling); if (!isPlainObject(value)) { return { field: 'period', message: `\`period\` must be { queried, held, ${readAt}? } — got ${shown(value)}.`, }; } const extra = unknownKey(value, ['queried', 'held', readAt], 'period'); if (extra !== undefined) return extra; if (value.queried === undefined || value.queried === null) { return { field: 'period.queried', message: '`period.queried` must say what the read asked for — { from, to }, two instants of the ' + 'READ that produced this result.', }; } const queried = spanProblem(value.queried, 'period.queried'); if (queried !== undefined) return queried; if (value.held !== 'unknown') { if (value.held === undefined || value.held === null) { return { field: 'period.held', message: "`period.held` must say what the store holds — { from, to }, or 'unknown' said out " + 'loud when the tool cannot vouch for it.', }; } const held = spanProblem(value.held, 'period.held'); if (held !== undefined) { return typeof value.held === 'string' ? { field: 'period.held', message: `\`period.held\` is ${shown(value.held)} — it must be { from, to } or the literal ` + "'unknown'.", } : held; } } const at = value[readAt]; if (at !== undefined && at !== null) { const bad = instantProblem(at, `period.${readAt}`); if (bad !== undefined) return bad; } return undefined; } exports.periodProblem = periodProblem; // ─── The doors ─────────────────────────────────────────────────────────── /** * Mint the wire form from an author's (camelCase) declaration — THROWS a * refusal (`refused: …`) on the first fault, at the line the author wrote (the * `absent()` law). `undefined` or `null` → `undefined`: not declared. A fresh * object, keys in wire order; nothing of the author's is held. */ function mintPeriod(declared) { if (declared === undefined || declared === null) return undefined; const problem = periodProblem(declared, 'camel'); if (problem !== undefined) throw (0, refusal_js_1.refusal)(problem.message); const period = declared; return { queried: { from: period.queried.from, to: period.queried.to }, held: period.held === 'unknown' ? 'unknown' : { from: period.held.from, to: period.held.to }, ...(period.readAt != null && { read_at: period.readAt }), }; } exports.mintPeriod = mintPeriod; /** * Read a wire period off a recognized envelope — never repaired. `{}` when * the envelope declares none (`undefined` or `null`, a JSON producer's * `None`); `{ period }` in the record's camelCase form when it is well formed; * `{ problem }` when it is not, so the caller can drop it from the record and * say so — the model still reads what the tool wrote. */ function readPeriod(wire) { if (wire === undefined || wire === null) return {}; const problem = periodProblem(wire, 'wire'); if (problem !== undefined) return { problem }; const onWire = wire; return { period: { queried: { from: onWire.queried.from, to: onWire.queried.to }, held: onWire.held === 'unknown' ? 'unknown' : { from: onWire.held.from, to: onWire.held.to }, ...(onWire.read_at != null && { readAt: onWire.read_at }), }, }; } exports.readPeriod = readPeriod; /** A declared period as detached plain data — the record's copy. */ function copyPeriod(period) { return { queried: { from: period.queried.from, to: period.queried.to }, held: period.held === 'unknown' ? 'unknown' : { from: period.held.from, to: period.held.to }, ...(period.readAt !== undefined && { readAt: period.readAt }), }; } exports.copyPeriod = copyPeriod; /** (declaration, tool) pairs already warned about — one warning per tool per PROCESS for each (dev mode only). */ const warnedTools = new Set(); const MAX_WARNED = 500; /** * Say, once per tool per process and in dev mode only, that a recognized * envelope's `period` — or an absence's `provenance` — could not be read and * was left off the record. The envelope itself is still recognized and served * as the tool wrote it — read, never repaired (the `items.ts` · * `readItemExtras` precedent). */ function warnDroppedDeclaration(toolName, declaration, problem) { const key = `${declaration} ${toolName ?? ''}`; if (warnedTools.has(key) || !(0, footprintjs_1.isDevMode)()) return; if (warnedTools.size < MAX_WARNED) warnedTools.add(key); // eslint-disable-next-line no-console console.warn(`agentfootprint coverage: tool '${toolName ?? '(unknown)'}' returned a ${declaration} the ` + `record cannot carry, so the ${declaration} was left off the record (the result itself is ` + `kept and served as written): ${problem.message} This warning fires once per tool per ` + `process.`); } exports.warnDroppedDeclaration = warnDroppedDeclaration; /** * Forget every dropped-declaration warning issued so far. * @internal test seam — the ledger is process-wide and warn-once. */ function _resetPeriodWarnings() { warnedTools.clear(); } exports._resetPeriodWarnings = _resetPeriodWarnings; // ─── The verdict ───────────────────────────────────────────────────────── /** * How far a declared period's READ covered what it asked for — ONE pure rule * over the instants the tool declared; bounds are inclusive: * * ```text * held === 'unknown' → 'unknown' * held.from ≤ queried.from and queried.to ≤ held.to → 'covered' * queried.to < held.from or queried.from > held.to → 'not-held' * otherwise → 'partly-held' * ``` * * No clock is read and no duration is parsed: the tool declared both spans. * Throws a `TypeError` naming the fault when handed a period that is not well * formed — a caller error; every period the library files was checked first. * * @example * ```ts * periodVerdict({ * queried: { from: '2026-09-26T09:00:00Z', to: '2026-09-26T10:00:00Z' }, * held: { from: '2026-08-27T02:00:00Z', to: '2026-09-26T02:00:00Z' }, * }); // 'not-held' — the data ends at 02:00; the hour asked about is after it * ``` */ function periodVerdict(period) { const problem = periodProblem(period, 'camel'); if (problem !== undefined) throw new TypeError(`periodVerdict: ${problem.message}`); if (period.held === 'unknown') return 'unknown'; const qFrom = (0, instant_js_1.instantOf)(period.queried.from, 'lenient'); const qTo = (0, instant_js_1.instantOf)(period.queried.to, 'lenient'); const hFrom = (0, instant_js_1.instantOf)(period.held.from, 'lenient'); const hTo = (0, instant_js_1.instantOf)(period.held.to, 'lenient'); if ((0, instant_js_1.compareInstants)(hFrom, qFrom) <= 0 && (0, instant_js_1.compareInstants)(qTo, hTo) <= 0) return 'covered'; if ((0, instant_js_1.compareInstants)(qTo, hFrom) < 0 || (0, instant_js_1.compareInstants)(qFrom, hTo) > 0) return 'not-held'; return 'partly-held'; } exports.periodVerdict = periodVerdict; /** * The verdict a CALL gets when its result declared more than one period (a * `coverage()` around an `absent()`, each with its own): the LEAST held of * them, so a reader is never told more than the weakest declaration covers — * `not-held`, then `partly-held`, then `unknown`, then `covered`. */ function leastHeld(verdicts) { for (const verdict of ['not-held', 'partly-held', 'unknown', 'covered']) { if (verdicts.includes(verdict)) return verdict; } return undefined; } exports.leastHeld = leastHeld; /** The key the served verdict word sits under, inside the served `period`. */ exports.SERVED_VERDICT_KEY = 'verdict'; /** * The ONE note clause per served verdict (honesty step 7b, bench round 1). * Static library text: no instant, no tool value, nothing interpolated — the * `ABSENCE_NOTE` law. Each states the CONSEQUENCE of its word in the terms the * notes already teach ("ground this result does NOT cover"), because the bench * showed a model repeating `period.queried` as the ground a result covered * without ever comparing it with `period.held`. */ exports.PERIOD_VERDICT_CLAUSES = Object.freeze({ 'not-held': '`period.verdict` is `not-held`: the store holds none of the time this read asked about ' + '(`period.queried`), so all of that time is ground this result does NOT cover — this ' + 'result settles nothing about it, and the time the store does hold is `period.held`.', 'partly-held': '`period.verdict` is `partly-held`: the store holds only part of the time this read asked ' + 'about (`period.queried`), so the part outside `period.held` is ground this result does ' + 'NOT cover — what this result says holds for `period.held` only.', unknown: '`period.verdict` is `unknown`: the tool cannot say what time its store holds, so this ' + 'result cannot vouch that it covers the time this read asked about (`period.queried`).', }); /** * What the model is served for one declared wire period — `undefined` when * there is nothing to add: no period, a period the rule set refuses (served * as the tool wrote it — read, never repaired), or a `covered` one. * * The verdict is {@link periodVerdict} over the instants the tool declared — * the one rule the record's `period` row is filed by, so the word the model * reads and the row the fold reads cannot disagree. Computed at the SERVE * door, never minted: the tool's own output keeps the bytes it declared, so a * helper in another language mints nothing new. * * @example * ```ts * servedPeriod({ * queried: { from: '2026-09-26T09:00:00Z', to: '2026-09-26T10:00:00Z' }, * held: { from: '2026-08-27T02:00:00Z', to: '2026-09-26T02:00:00Z' }, * })?.period; // { queried: {…}, held: {…}, verdict: 'not-held' } * ``` */ function servedPeriod(wire) { const { period } = readPeriod(wire); if (period === undefined) return undefined; const verdict = periodVerdict(period); if (verdict === 'covered') return undefined; const onWire = mintPeriod(period); return { period: { ...onWire, [exports.SERVED_VERDICT_KEY]: verdict }, verdict, clause: exports.PERIOD_VERDICT_CLAUSES[verdict], }; } exports.servedPeriod = servedPeriod; /** * The inverse, for a reader of what the model was SERVED: a wire period with * its served verdict word removed — when that word is exactly the one * {@link servedPeriod} computes from the rest. Anything else (no word, a word * that disagrees with the instants, a period the rule set refuses) comes back * as the same reference, so the rule set still names it: a verdict is derived * by the library, never declared by a tool. */ function unservedPeriod(wire) { if (!isPlainObject(wire) || !(exports.SERVED_VERDICT_KEY in wire)) return wire; const { [exports.SERVED_VERDICT_KEY]: word, ...declared } = wire; return servedPeriod(declared)?.verdict === word ? declared : wire; } exports.unservedPeriod = unservedPeriod; /** * A served note with its period clause removed (the inverse of appending * {@link PERIOD_VERDICT_CLAUSES}`[verdict]`), or the note as found. */ function noteWithoutClause(note, verdict) { if (typeof note !== 'string') return note; const clause = exports.PERIOD_VERDICT_CLAUSES[verdict]; if (note === clause) return undefined; return note.endsWith(` ${clause}`) ? note.slice(0, -(clause.length + 1)) : note; } exports.noteWithoutClause = noteWithoutClause; /** A served note: the note as found, then the period clause. */ function noteWithClause(note, clause) { return typeof note === 'string' && note !== '' ? `${note} ${clause}` : clause; } exports.noteWithClause = noteWithClause; // ─── The person's line ─────────────────────────────────────────────────── /** * The `Period:` line the limits block prints for one declaring call — the * period AS THE TOOL DECLARED IT, instants verbatim (no reformatting, no zone * conversion, no verdict word: the answer's standing owns the verdict's * sentence). Static words around the tool's own values. With a * `presentation` (the run's clock zone, only under `.time()`), the instants * are rendered in that zone instead — see {@link periodLineInZone}. * * @example * ```ts * periodLine('backup_runs', { * queried: { from: '2026-09-26T09:00:00Z', to: '2026-09-26T10:00:00Z' }, * held: { from: '2026-08-27T02:00:00Z', to: '2026-09-26T02:00:00Z' }, * }); * // 'backup_runs queried 2026-09-26T09:00:00Z to 2026-09-26T10:00:00Z; the store holds * // 2026-08-27T02:00:00Z to 2026-09-26T02:00:00Z' * ``` */ function periodLine(toolName, period, presentation) { if (presentation !== undefined) return periodLineInZone(toolName, period, presentation); const queried = `${toolName} queried ${period.queried.from} to ${period.queried.to}`; const held = period.held === 'unknown' ? 'what the store holds is unknown' : `the store holds ${period.held.from} to ${period.held.to}`; const readAt = period.readAt !== undefined ? ` (read at ${period.readAt})` : ''; return `${queried}; ${held}${readAt}`; } exports.periodLine = periodLine; /** * The same line under `.time()` (time design § 10.2): every instant rendered * in the presentation zone by the time layer's one renderer * (`time/present.ts`), the zone named, each end AS DECLARED (a declared * period is inclusive, so no end moves). The raw instants stay in the typed * record (`coverageDeclared`, `answerCoverage`); a label is never parsed back. * * @example * ```ts * periodLine('backup_runs', { * queried: { from: '2026-09-26T09:00:00Z', to: '2026-09-26T10:00:00Z' }, * held: 'unknown', * }, bindPresentation({ zone: 'America/Los_Angeles' })); // time/present.ts * // 'backup_runs queried 2026-09-26 02:00–03:00 America/Los_Angeles (UTC-07:00); * // what the store holds is unknown' * ``` */ function periodLineInZone(toolName, period, presentation) { const queried = `${toolName} queried ${presentation.span(period.queried.from, period.queried.to)}`; const held = period.held === 'unknown' ? 'what the store holds is unknown' : `the store holds ${presentation.span(period.held.from, period.held.to)}`; const readAt = period.readAt !== undefined ? `; read at ${presentation.instant(period.readAt)}` : ''; return `${queried}; ${held}${readAt}`; } /** Whether a value is a {@link HeldSpan} the rule set accepts — the checkpoint door's test. */ function isHeldSpan(value) { if (!isPlainObject(value)) return false; return (spanProblem(value.queried, 'held.queried') === undefined && spanProblem(value.held, 'held.held') === undefined && Object.keys(value).every((k) => k === 'queried' || k === 'held')); } /** Every verdict a period row may carry, in the order the fold reports them. */ exports.PERIOD_ROW_VERDICTS = Object.freeze([ 'covered', 'partly-held', 'not-held', 'unknown', 'undeclared', ]); /** * The checkpoint door's test for a `period` row (`core/runCheckpoint.ts` · * `ledgerRowIsWellFormed`) — the one owner of the row's shape, so the door * refuses exactly what this module never files. */ function periodRowIsWellFormed(row) { return (row.kind === 'period' && typeof row.turn === 'number' && typeof row.toolCallId === 'string' && typeof row.toolName === 'string' && typeof row.iteration === 'number' && typeof row.verdict === 'string' && exports.PERIOD_ROW_VERDICTS.includes(row.verdict) && (row.argument === undefined || (typeof row.argument === 'string' && row.argument !== '')) && (row.differs === undefined || (0, checkRecord_js_1.isPeriodDiffers)(row.differs)) && (row.shifted === undefined || (0, checkRecord_js_1.isShifted)(row.shifted)) && (row.beyondRetention === undefined || row.beyondRetention === true) && (row.partlyBeyondRetention === undefined || row.partlyBeyondRetention === true) && (row.held === undefined || isHeldSpan(row.held))); } exports.periodRowIsWellFormed = periodRowIsWellFormed; // ─── The time layer's limits lines (step T8) ───────────────────────────── /** * The one limits line for a call whose `period` row carries a time check * (time design § 10.2, step T8) — `undefined` when it carries none. Static * words around the two ranges, rendered in the presentation zone by the time * layer's one renderer, HANDED in bound (`time/present.ts` · * `bindPresentation` → `range`, to the second, the zone named — coverage never * imports the renderer); the raw instants stay on the row. * * @example * ```ts * periodCheckLine(row, bindPresentation({ zone: 'America/Los_Angeles' })); // time/present.ts * // 'search_logs read more than was asked — asked: 2026-10-08 00:00:00–23:59:59 America/Los_Angeles * // (UTC-07:00); read: 2026-10-08 00:00:00–2026-10-09 08:40:00 America/Los_Angeles (UTC-07:00)' * ``` * * `audience` names who reads the line: the PERSON (the limits block, the * default — their window is "your window") or the MODEL (the served time line, * `agent/arguments/serve.ts` · `timeLimitsSentence` — the same window is "the * person's window"). That reference differs, and a SHIFTED read: the model is * handed the library's conclusion ({@link shiftedConclusion}) — which part of * the window the result does not cover, in the person's zone, and that the * answer says so — not the two ranges to compare (T8 bench: after a pause the * two-range line did not stop "1 error in the last 30 minutes", 11/20). The * person's line keeps its bytes. */ function periodCheckLine(row, presentation, audience = 'person') { const parts = []; const d = row.differs; if (d !== undefined && audience === 'model' && row.shifted !== undefined && d.missing.length > 0 && d.extra.length > 0) { parts.push(shiftedConclusion(row.toolName, d, presentation)); } else if (d !== undefined) { const person = d.against === 'person'; const theirs = audience === 'model' ? "the person's window" : 'your window'; const reference = person ? theirs : 'was asked'; const what = d.missing.length > 0 && d.extra.length > 0 ? row.shifted !== undefined ? 'a shifted window' : 'a different window' : `${d.missing.length > 0 ? 'less' : 'more'} than ${reference}`; const reads = d.read.map((r) => presentation.range(r, 'second')).join('; '); parts.push(`${row.toolName} read ${what} — ${person ? theirs : 'asked'}: ` + `${presentation.range(d.asked, 'second')}; read: ${reads}`); } else if (row.shifted !== undefined) { parts.push(`${row.toolName}'s look-back ran after the clock moved on`); } if (row.beyondRetention === true) { parts.push(`${row.toolName}: the time asked about is older than the oldest data the tool declares its source keeps`); } return parts.length === 0 ? undefined : parts.join('. '); } exports.periodCheckLine = periodCheckLine; // LENS · late-line · request-ephemeral // reads: one `period` row's `differs` (asked, missing, extra) and `shifted` — a look-back that ran // after the turn's clock moved on — rendered in the person's zone by the bound renderer // law: the library's CONCLUSION for the MODEL: the part of the window the result does not cover, // and that the answer says so and claims nothing about it; never two ranges left to compare. /** * The model's line for a SHIFTED read (`period-shifted` with both `missing` * and `extra`): the part of the asked window (or the person's) the result * does not cover and the time it read outside it, in the presentation zone, * then what the answer does with that — says it, and claims nothing about it * from this tool. * * @example * ```ts * shiftedConclusion('search_logs', differs, presentation); * // "search_logs's look-back ran after the clock moved on, so its result does not cover * // 2026-10-09 09:30:00–09:59:59 America/Los_Angeles (UTC-07:00) of the person's window (…), * // and covers …, outside it. So the answer says that search_logs's result does not cover * // 2026-10-09 09:30:00–09:59:59 America/Los_Angeles (UTC-07:00), and claims nothing about that * // time from it" * ``` */ function shiftedConclusion(toolName, d, presentation) { const ranges = (rs) => rs.map((r) => presentation.range(r, 'second')).join(' and '); const reference = d.against === 'person' ? "the person's window" : 'the window asked'; const missing = ranges(d.missing); const asked = ranges([d.asked]); const none = missing === asked; const gap = none ? `any of ${reference}, ${asked}` : `${missing} of ${reference} (${asked})`; return (`${toolName}'s look-back ran after the clock moved on, so its result does not cover ${gap}, ` + `and covers ${ranges(d.extra)}, outside it. So the answer says that ${toolName}'s result ` + `does not cover ${missing}, and claims nothing about that time from it`); } /** * The `Clocks` lines (time design § 9.6, § 10.2, step T8): one per wall-clock * source — a dataset whose declared axis names a zone — and, when two sources * declare different zones, the label that they differ. Every comparison is * made on instants; the lines only name the clocks. */ function clockLines(sources, differ) { const lines = sources.map((s) => `${s.toolName}'s rows are wall times in ${s.zone} (declared) — compared as instants`); if (differ !== undefined) { lines.push(`the sources' clocks differ (${differ.zones.join(', ')}) — compared as instants`); } return lines; } exports.clockLines = clockLines; //# sourceMappingURL=period.js.map