/** * Query-before-derive (QBD) event writers (mmnto-ai/totem#2510). * * Two row types, one contract: * * - `recordCorpusQuery` — a corpus query fired. Mints a fresh correlation ID * **from the same clock read that stamps the row's `timestamp`**, so the ID * and the row are born together. Then parks the ID in a pointer file for the * derives that follow. * - `recordDeriveAction` — a derive-class action ran. Reads the parked ID and * attaches it *only if* it is still inside the correlation window, from this * session, and from this seat. Otherwise the row is written with NO * correlation ID — an uncorrelated derive is the metric's most important * observation, so it is always recorded, never dropped (#2510 falsifier 1). * * ## Consumption semantics: one query grounds ONE derive * * The pointer is CONSUMED by the first derive that correlates to it. Without * that, a single query would credit every derive for the rest of the * correlation window — one query, ten derives, compliance 1.00 — which measures * "queried at least once per two hours", not "queried before deriving". The * strict 1:1 reading is the one that can actually be falsified, so it is the * one implemented. A second derive after a single query is UNCORRELATED, and * that is the correct reading, not a miss. * * ## Correlation is scoped to one seat AND one session, fail-closed * * Both sides must carry a session id and agree on it, and the seats must match. * Cohort seats share one working tree per repo, so a pointer left by another * seat is reachable; treating a missing id as a match would let it ground this * seat's derive. * * **Seat comparison is equality including "both unknown".** When neither side * is seated (`TOTEM_SELF_AGENT` unset), they compare equal and correlation * proceeds. That is the deliberate default for the solo/unseated case, which is * the common one — requiring a seat would make the metric unusable for anyone * not running a cohort. The cost is disclosed rather than hidden: seat exactly * ONE side and correlation stops entirely. That is a live configuration today — * the CLI can be seated while the MCP server is not — and it produces a * truthful-looking 0.00 whose pre-registered consequence is falsified adherence * claims. The scanner therefore emits a seat-mismatch hint (see * `compliance.ts`) so the config smell is visible instead of silent. Wiring the * MCP server's seat is out of this slice's scope and tracked in * mmnto-ai/totem#2530. * * ## Why minting lives here and nowhere else * * `mintQbdCorrelationId(now)` takes the instant as an argument, which would let * a caller pass any instant it liked. Every production writer goes through this * module, where `now` is read once and used for BOTH the ID and the timestamp. * A caller that tried to route around it would have to hand-build a row, and the * schema refinement in `ledger.ts` rejects any row whose ID could not have been * minted when the row was written. Convention here, enforcement in the schema. * * ## Failure posture (Tenet 13 + ADR-115 § 2) * * A contract breach — a pointer file holding a forged, malformed, or * out-of-window ID — makes the schema reject the row, and these functions THROW * (the loud backstop; a backfilled ID is a schema violation, not a data point). * An ordinary I/O failure does not throw; it is reported through the returned * `warnings` array. * * Neither may break the instrumented command. That is what the `sense*` * wrappers are for: they catch the backstop, convert it to a visible warning, * and return. Command call sites use `sense*`; the throwing variants exist so * the contract is testable and so a programming error is never silent. */ /** Which surface fired a `corpus_query`. Stamped as `activity_name`. */ export type QbdQuerySurface = 'totem_search' | 'search_knowledge'; /** Which derive-class action ran. Stamped as `activity_name`. */ export type QbdDeriveSurface = 'spec' | 'orient' | 'review'; export interface QbdRecordInput { /** Absolute path to the resolved `.totem` directory. */ totemDir: string; /** Emitting subsystem: `lint` for CLI commands, `bot` for the MCP server. */ source: 'lint' | 'bot'; /** Test seam — production callers omit and the writer reads the clock. */ nowMs?: number; /** Test seam — production callers omit and the writer reads `process.env`. */ env?: NodeJS.ProcessEnv; } export interface QbdRecordResult { /** True when the ledger row reached disk. */ written: boolean; /** * True when nothing was recorded because this is not an instrumented project * (no `.totem` directory). Distinguishes "nothing to instrument here" — a * normal state — from "the write failed", which is a degradation. */ skipped?: boolean; /** The correlation ID on the row, when it carries one. */ correlationId?: string; /** * Per-item accounting. Every non-fatal degradation names itself here; the * caller renders these so a sensor failure is visible, never silent * (ADR-115 § 2 accounting contract). */ warnings: string[]; } /** * Resolve the emitting seat from `TOTEM_SELF_AGENT`, reusing the comma-split * precedent of `deriveSearchLogAttribution` / `resolveSelfAgents` — a process * runs under one seat, so the first non-empty entry is that seat. */ export declare function resolveQbdAgentSource(env: NodeJS.ProcessEnv): string | undefined; /** * Record a corpus query and mint its correlation ID. * * `nowMs` is read ONCE and used for both the ID's embedded mint instant and the * row's `timestamp` — the property the schema refinement checks. */ export declare function recordCorpusQuery(input: QbdRecordInput & { surface: QbdQuerySurface; }): QbdRecordResult; /** * Record a derive-class action, attaching the correlation ID of the query that * grounded it when one is in scope. * * Throws on a correlation-contract breach — AFTER the derive row has been * written uncorrelated. The ledger is left correct either way; the throw is the * loud backstop that stops a tampered pointer from passing as a data point. */ export declare function recordDeriveAction(input: QbdRecordInput & { surface: QbdDeriveSurface; }): QbdRecordResult; /** Sensor-safe `recordCorpusQuery`. Command call sites use this. */ export declare function senseCorpusQuery(input: QbdRecordInput & { surface: QbdQuerySurface; }, onWarn?: (msg: string) => void): QbdRecordResult; /** * Sensor-safe `recordDeriveAction`. Command call sites use this. * * A correlation-contract breach is reported as a warning rather than thrown — * and, critically, the derive row it refused to correlate has still been * written, so `written` stays true and the denominator is intact. */ export declare function senseDeriveAction(input: QbdRecordInput & { surface: QbdDeriveSurface; }, onWarn?: (msg: string) => void): QbdRecordResult; //# sourceMappingURL=record.d.ts.map