/** * Query-before-derive compliance — the metric (mmnto-ai/totem#2510). * * ## The number * * Of the derive-class actions in an agent session (spec synthesis, orientation * derivation, review grounding), what fraction was preceded by a corpus query * correlated to that action? * * compliance = correlated derive events / ALL derive events * * ## Denominator discipline (#2510 falsifier 1) * * The denominator is **every** derive-class event in the evaluated sessions, * including sessions that fired zero queries. A session that derived without * ever querying is the exact behaviour this metric exists to catch, so it must * pull the number DOWN, not vanish from it. Two places this could have been * gamed, and how each is closed: * * - *at the ratio level* — counting only derives that carry an ID. Closed: the * denominator counts derive rows, correlated or not. * - *at the window level* — enumerating only sessions that contain queries. * Closed: session selection keys on "carries ≥1 derive-class event, * regardless of query count", which is the operator-pinned window phrasing. * * ## Numerator discipline (#2510 falsifier 2) * * A derive counts as correlated only when its `qbd_correlation_id` resolves to * a `corpus_query` row that actually appears earlier in the ledger, in the same * session and from the same seat. Ways that check fails, all counted as * anomalies and none as compliant: a schema-rejected ID (see * `correlation-id.ts`), an ID with no matching query row (orphan — a truncated * or tampered ledger), an ID whose query row belongs to a different session or * a different seat, and a row whose timestamp regresses in an append-only file * (a backdated append trying to seize the evaluation window). * * One query grounds exactly ONE derive — the pointer is consumed on use (see * `record.ts`). Without that, a single query would credit every derive for the * rest of the correlation window. * * ## What this metric CANNOT see (#2510 falsifier 4, ritual query) * * A query fired purely to satisfy the metric, whose results the following * derive never reads, is indistinguishable here from a query that genuinely * grounded the derive. Both produce a query row and a correlated derive row. * v1 senses adjacency, not influence. This is named, not solved — the honest * statement is that the number measures whether querying happened before * deriving, NOT whether the derive used what the query returned. Any reading * that treats it as the latter is over-claiming. * * ## Degraded reads (ADR-115 § 2) * * `readLedgerEvents` skips schema-invalid lines silently, which would let a torn * or tampered ledger render as a confident 100% or a confident 0%. This scanner * therefore parses the NDJSON itself and counts every rejected line by class. A * scan with any integrity anomaly is `degraded`, and the render must announce * that rather than print a bare number. */ /** Pre-registered floor. Below this at the checkpoint, the claim is falsified. */ export declare const QBD_PRE_REGISTERED_THRESHOLD = 0.5; /** Pre-registered evaluation window, in sessions. */ export declare const QBD_PRE_REGISTERED_WINDOW_SESSIONS = 20; /** * The pre-registration, verbatim as pinned by the operator ruling 2026-07-28 on * mmnto-ai/totem#2510. Rendered verbatim by the doctor section — the exact * phrasing is load-bearing: an earlier "first 20 correlated sessions" wording * was post-hoc-interpretable as excluding zero-query sessions, which would have * enacted falsifier 1 at the window level. Do not paraphrase this string. */ export declare const QBD_PRE_REGISTRATION_STATEMENT = "compliance \u2265 0.50, evaluated over the first 20 instrumented sessions carrying \u22651 derive-class event, regardless of query count"; export type QbdEventType = 'corpus_query' | 'derive_action'; /** A QBD row lifted out of the ledger. */ export interface QbdRow { ms: number; type: QbdEventType; activityName?: string; sessionId?: string; correlationId?: string; agentSource?: string; } /** Per-item anomaly accounting — every rejected or unjoinable item, by class. */ export interface QbdAnomalies { /** Lines that were not valid JSON (a torn append, a hand-edit). */ malformedJson: number; /** * Lines that parsed as JSON but were rejected by the ledger schema while * looking like QBD rows — this is where a backfilled correlation ID lands. */ correlationContractViolations: number; /** Lines rejected by the schema whose event type could not be classified. */ unclassifiedInvalid: number; /** Derive rows whose correlation ID matches no earlier query row. */ orphanCorrelations: number; /** Derive rows whose correlated query row belongs to a different session. */ crossSessionCorrelations: number; /** * Derive rows citing a correlation ID an earlier derive already spent. One * query grounds one derive; a re-citation means the write-side consume * failed, raced, or was bypassed. */ duplicateCorrelations: number; /** * Uncorrelated derives that had an in-window query from a DIFFERENT seat — * the signature of one-sided seat plumbing. A diagnostic hint, never an * integrity anomaly, so it does not degrade the read. */ seatMismatchHints: number; /** * Rows whose timestamp regresses behind the newest already seen in this * append-only file — the backdated-append attack on the evaluation window. */ backdatedRows: number; /** * Rows carrying an event type this build does not know. An ADVISORY, not an * integrity anomaly: the ordinary cause is version skew, and it deliberately * does NOT flip `degraded`. */ unknownTypeRows: number; /** Human-readable detail lines, capped, for the render. */ details: string[]; } export interface QbdScanResult { rows: QbdRow[]; /** Non-empty lines examined. */ linesScanned: number; anomalies: QbdAnomalies; /** * True when any integrity anomaly fired. A degraded scan must never render as * a clean number (ADR-115 § 2) — the number it would print is not trustworthy * because rows are known to be missing or rejected. */ degraded: boolean; } export interface QbdRateStat { /** Derive-class events counted (the denominator). */ derives: number; /** Of `derives`, how many were correlated to a preceding query. */ correlated: number; } export type QbdVerdict = 'PENDING' | 'PASS' | 'FAIL'; export interface QbdComplianceReport { /** Sessions carrying ≥1 derive-class event, in first-seen order. */ instrumentedSessions: number; /** Sessions actually evaluated (capped at the pre-registered window). */ evaluatedSessions: number; /** The pre-registration window's rate. */ window: QbdRateStat; /** The rate, or null when there is nothing to divide. */ compliance: number | null; /** * `PENDING` until the window fills — the pre-registered threshold is not * evaluable before then, and calling it early in either direction would be * exactly the post-hoc reinterpretation the registration forbids. */ verdict: QbdVerdict; /** Trend across all instrumented sessions, earlier half vs recent half. */ trend: { earlier: QbdRateStat; recent: QbdRateStat; } | null; anomalies: QbdAnomalies; degraded: boolean; } /** * Parse the raw `events.ndjson` contents, lifting out QBD rows and counting * every line this metric could not trust. * * Deliberately does NOT use `readLedgerEvents`: that helper drops schema-invalid * lines without telling anyone, which is precisely how a tampered ledger would * render as a clean number. */ export declare function scanQbdLedger(content: string): QbdScanResult; /** * One instrumented session: the unit the pre-registered evaluation window * counts in. Exported because `groupQbdSessions` returns it through the package * barrel — a consumer could call that function but not name its return type. */ export interface QbdSession { /** Stable identity: `sid::` or `win::`. */ key: string; /** Earliest row instant in the session; sessions are ordered by this. */ firstMs: number; /** The session's rows, in time order. */ rows: QbdRow[]; } /** * Group rows into sessions. * * Rows carrying a `session_id` group by it. Rows without one (hookless agents, * pre-hook runs) fall back to a rolling-window roll-up, applied among * themselves and PARTITIONED BY `agent_source`. Both are "instrumented * sessions"; neither is privileged. * * The agent partition matters concretely: cohort seats share one working tree * per repo, so two seats writing to the same ledger inside the same two-hour * span would otherwise be rolled into a single "session", letting one seat's * query sit in the same session as another seat's derive. Sessions are * per-agent, so the fallback is too. * * Note on provenance: preferring an explicit session id over a time heuristic * follows the `.session-id` primitive's own documented contract * (`session-id.ts`), not ADR-029 § 2 — that section defines a session-GROUPING * heuristic for the recall metric, which is a different question. */ export declare function groupQbdSessions(rows: QbdRow[]): QbdSession[]; /** * Compute the compliance report from a scan. * * The verdict stays `PENDING` until the pre-registered window fills. That is * deliberate: declaring PASS at n=3 would be as much a post-hoc reinterpretation * of the registration as moving the threshold would be. */ export declare function computeQbdCompliance(scan: QbdScanResult): QbdComplianceReport; /** Format a rate as a fixed-2 fraction, or `n/a` when there is nothing to divide. */ export declare function formatQbdRate(stat: QbdRateStat): string; //# sourceMappingURL=compliance.d.ts.map