/** * Per-seat selection manifests — the Prop 308 / strategy#467 M1 instrument * (mmnto-ai/totem#2468). * * A selection manifest is a read-only record of one selection event: which * candidates a consumer (session-start hook, `search_knowledge`, `totem * orient`) considered on a seat's behalf, which it selected or excluded, what * each cost, and the policy's own stated reason. It is a record of what each * policy did — never a ranker, and it never adjudicates between policies. * * ## Contract (binding, from the Prop 308 round + the armed #467 pre-registration) * * - **No shared relevance score, no common weights** (Prop 308 F1, unanimous; * lc-codex refute condition). Both schema levels are `.strict()` so a shared * score field cannot be added without failing parse of existing rows — the * guard is structural, not a promise. Per-candidate `reason` strings carry * each policy's OWN words (a floor value in a search reason is that policy's * stated reason, not a common weight). * - **Senses, never gates** (Tenet 13). Emission must never block, filter, or * alter a selection. Command call sites use {@link senseSelectionManifest}; * the throwing writer exists so the contract is testable and a programming * error is never silent (the `qbd/record.ts` posture, mirrored). * - **Named cost basis** (#467 outcome 3's naming requirement): every figure * names its aggregation — {@link SELECTION_COST_BASIS} is a fixed literal * stamped on every row. What the manifest emits is a PAYLOAD-BYTE census * with a declared token approximation; it is NOT the ruled `` basis * (measured cache-aware consumption), and whether it may substitute for a * `` surface is the pre-registration's call via versioned revision * (#467 §9), never this module's claim. * - **No fabricated measurement:** a manifest records only what the policy * observed. Candidates the policy never read (recency-excluded journals, * first-match-losing proposals) are recorded id-only — no fingerprint, no * bytes. Emitters never over-fetch to manufacture exclusions, which would * alter the thing being measured. * - **Recorded absence, never silent skip** (the #2466 clause): the * denominator lives in `events.ndjson` rows (`session_start`, `mcp_call`, * `derive_action`) that ship independently of this instrument. A denominator * row with no matching manifest row IS the recorded absence; a failed write * additionally warns on the emitter's accounting channel. */ import { z } from 'zod'; /** * Sidecar file, NOT rows in `events.ndjson` (ruled 2026-08-10, #2468 OQ1): * per-candidate arrays are bulky, and the event stream's existing consumers * (QBD scanner, stats) keep their scan economics. Same directory, same * append-only + gitignored conventions; growth is bounded the same way * `events.ndjson` is (janitorial rotation is a separate, pre-existing class). */ export declare const SELECTION_MANIFESTS_FILE = "selection-manifests.ndjson"; /** * The named aggregation stamped on every row (#467 outcome-3 requirement: * a measure without its basis reports NOT OBSERVED — so the basis rides the * data). `bytes` is the UTF-8 byte length of the exact content the policy * considered; `approxTokens` is declared as the chars/4 approximation, never * presented as a measured token count. */ export declare const SELECTION_COST_BASIS: { readonly bytes: "utf8-length"; readonly approxTokens: "ceil(bytes/4) approximation"; }; /** Emitting consumers — the three selection surfaces named by #2468. */ export declare const SELECTION_EMITTERS: readonly ["session-start", "search_knowledge", "orient"]; export type SelectionEmitter = (typeof SELECTION_EMITTERS)[number]; /** * One candidate the policy observed. `.strict()` — see the module contract: * no field can be added silently, which is the structural half of the Prop 308 * refute-condition guard. */ export declare const SelectionCandidateSchema: z.ZodEffects; /** * sha256 hex, first 16 chars, over the EXACT bytes the policy considered. * Absent when the policy never read the content (id-only exclusion) or * when hashing failed (which also appends to `warnings` — an absent * fingerprint is honest, a fabricated one never is). */ fingerprint: z.ZodOptional; /** UTF-8 byte length of the considered content. Absent on id-only rows. */ bytes: z.ZodOptional; /** ceil(bytes/4), under the declared approximation basis. */ approxTokens: z.ZodOptional; /** * What the policy did with this candidate. `truncated` = selected but * partially delivered (`deliveredBytes` carries what actually shipped). */ disposition: z.ZodEnum<["selected", "truncated", "excluded"]>; /** * The policy's own stated reason — the why-selected / why-excluded half * that is invisible today. Free text owned by the emitting policy. */ reason: z.ZodString; /** For `truncated` rows: bytes actually delivered after the cut. */ deliveredBytes: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }>, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }>; export type SelectionCandidate = z.infer; /** * The CLOSED set of per-emitter context keys. Closing the key space is the * context-layer half of the Prop 308 refute-condition guard (PR #2625 CR * round): with an open record, `context.sharedRelevanceScore` would have * parsed cleanly and the "no shared score without a schema change" claim held * only at the row/candidate layers. Adding a context key is now a deliberate * schema change, exactly like adding a field. */ export declare const SELECTION_CONTEXT_KEYS: readonly ["query", "boundary", "typeFilter", "floor", "finalLimit", "method", "status", "storesFailed", "storesUnavailable", "faulted", "render", "branch", "ticket", "selfAgent", "template"]; export type SelectionContextKey = (typeof SELECTION_CONTEXT_KEYS)[number]; /** One selection event — one NDJSON row in `selection-manifests.ndjson`. */ export declare const SelectionManifestRowSchema: z.ZodObject<{ schemaVersion: z.ZodLiteral<1>; /** ISO 8601 instant of the selection event. */ timestamp: z.ZodString; emitter: z.ZodEnum<["session-start", "search_knowledge", "orient"]>; /** Session UUID from `.totem/ledger/.session-id` — the denominator join key. */ session_id: z.ZodOptional; /** Seat id from TOTEM_SELF_AGENT (ADR-078). Absent = stamped absence, never guessed. */ agent_source: z.ZodOptional; /** * Provenance of `agent_source` (mmnto-ai/totem#2629, ruled two-value enum): * `'env'` iff the row was stamped from `TOTEM_SELF_AGENT`, `'absent'` iff * no attribution was stamped. Schema-optional for pre-sensor rows (the * ADR-078 additive migration shape); {@link buildSelectionManifestRow} * stamps it on every new row. */ agent_source_provenance: z.ZodOptional>; /** * Fail-closed disclosure (#2629): present iff the ambient env seat and the * pointer session's minting seat disagreed — `agent_source` is withheld and * both candidates are named so a post-hoc reader can partition the window * without guessing. Never present on self-minted rows (the minting seat IS * the emitting process's own env read there). */ attribution_conflict: z.ZodOptional>; /** Named degradation (#2629): the conflict probe failed; the env stamp stands. */ attribution_probe_error: z.ZodOptional; /** Emitting package version, when the emitter can resolve it cheaply. */ cli_version: z.ZodOptional; context: z.ZodRecord, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>; /** * Names the observable candidate pool (e.g. `store-returned-pool * perStoreLimit=5`) — the honest boundary: exclusions are recorded only * over candidates the emitter actually saw. */ universe: z.ZodString; costBasis: z.ZodObject<{ bytes: z.ZodLiteral<"utf8-length">; approxTokens: z.ZodLiteral<"ceil(bytes/4) approximation">; }, "strict", z.ZodTypeAny, { bytes: "utf8-length"; approxTokens: "ceil(bytes/4) approximation"; }, { bytes: "utf8-length"; approxTokens: "ceil(bytes/4) approximation"; }>; /** * The session-start hook's global char slice — string-level, so its cut * is NOT attributed per-candidate (declared limitation, not a gap). */ finalTruncation: z.ZodOptional>; candidates: z.ZodArray; /** * sha256 hex, first 16 chars, over the EXACT bytes the policy considered. * Absent when the policy never read the content (id-only exclusion) or * when hashing failed (which also appends to `warnings` — an absent * fingerprint is honest, a fabricated one never is). */ fingerprint: z.ZodOptional; /** UTF-8 byte length of the considered content. Absent on id-only rows. */ bytes: z.ZodOptional; /** ceil(bytes/4), under the declared approximation basis. */ approxTokens: z.ZodOptional; /** * What the policy did with this candidate. `truncated` = selected but * partially delivered (`deliveredBytes` carries what actually shipped). */ disposition: z.ZodEnum<["selected", "truncated", "excluded"]>; /** * The policy's own stated reason — the why-selected / why-excluded half * that is invisible today. Free text owned by the emitting policy. */ reason: z.ZodString; /** For `truncated` rows: bytes actually delivered after the cut. */ deliveredBytes: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }>, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }, { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }>, "many">; /** Per-row accounting: every non-fatal degradation names itself here. */ warnings: z.ZodArray; }, "strict", z.ZodTypeAny, { schemaVersion: 1; timestamp: string; warnings: string[]; context: Partial>; emitter: "orient" | "search_knowledge" | "session-start"; universe: string; costBasis: { bytes: "utf8-length"; approxTokens: "ceil(bytes/4) approximation"; }; candidates: { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }[]; agent_source?: string | undefined; session_id?: string | undefined; cli_version?: string | undefined; agent_source_provenance?: "env" | "absent" | undefined; attribution_conflict?: { env_seat: string; minting_seat: string; pointer_session_id: string; } | undefined; attribution_probe_error?: string | undefined; finalTruncation?: { cap: number; totalChars: number; applied: boolean; } | undefined; }, { schemaVersion: 1; timestamp: string; warnings: string[]; context: Partial>; emitter: "orient" | "search_knowledge" | "session-start"; universe: string; costBasis: { bytes: "utf8-length"; approxTokens: "ceil(bytes/4) approximation"; }; candidates: { reason: string; id: string; disposition: "truncated" | "selected" | "excluded"; sourceRepo?: string | undefined; fingerprint?: string | undefined; bytes?: number | undefined; approxTokens?: number | undefined; deliveredBytes?: number | undefined; }[]; agent_source?: string | undefined; session_id?: string | undefined; cli_version?: string | undefined; agent_source_provenance?: "env" | "absent" | undefined; attribution_conflict?: { env_seat: string; minting_seat: string; pointer_session_id: string; } | undefined; attribution_probe_error?: string | undefined; finalTruncation?: { cap: number; totalChars: number; applied: boolean; } | undefined; }>; export type SelectionManifestRow = z.infer; /** * Fingerprint the exact bytes a policy considered: sha256 hex, first 16. * 64 bits of collision resistance is ample for a per-workspace corpus join * key; the full digest would double every row's bulk for no analytical gain. */ export declare function fingerprintContent(content: string | Buffer): string; /** Cost of a considered candidate under {@link SELECTION_COST_BASIS}. */ export declare function measureCandidateCost(content: string | Buffer): { bytes: number; approxTokens: number; }; /** * Build a fully-measured candidate from content the policy actually read. * * TOTAL over its input space (leg round 1, H-7): a non-string/non-Buffer * `content` (a store row whose unchecked cast lied) or a hash failure * degrades to an ID-ONLY row with the failure named on the returned warning — * it never throws into the emitting command, and it never persists a * fabricated or NaN measurement. */ export declare function buildMeasuredCandidate(input: { id: string; content: string | Buffer; disposition: SelectionCandidate['disposition']; reason: string; sourceRepo?: string; deliveredBytes?: number; }): { candidate: SelectionCandidate; warning?: string; }; export interface SelectionManifestInput { /** Absolute path to the resolved `.totem` directory. */ totemDir: string; emitter: SelectionEmitter; context: Partial>; universe: string; candidates: SelectionCandidate[]; warnings?: string[]; finalTruncation?: { cap: number; totalChars: number; applied: boolean; }; cliVersion?: string; /** 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; /** * Session UUID for emitters that minted the id themselves (the * session-start hooks). When absent, the id is read from the * `.totem/ledger/.session-id` pointer. * * CONTRACT (#2629): passing this field asserts THIS PROCESS minted the id — * it disables the attribution conflict probe, because a self-minted row's * minting seat is by construction the emitting process's own env read. A * caller that merely RESOLVED an id from somewhere else (env, a foreign * pointer) must NOT pass it here, or it silently opts out of the sensor. */ sessionId?: string; } export interface SelectionManifestResult { /** True when the manifest row reached disk. */ written: boolean; /** * True when nothing was recorded because this is not an instrumented * project (no `.totem` directory) — a normal state, not a degradation. */ skipped?: boolean; warnings: string[]; } /** Stamp attribution + basis onto an emitter's assembled input. */ export declare function buildSelectionManifestRow(input: SelectionManifestInput): SelectionManifestRow; /** * Validate and append one manifest row. * * Throws `SELECTION_MANIFEST_CONTRACT` on a schema-invalid row — that is a * programming error in an emitter, and persisting it would hand the * measurement pass a row `readSelectionManifests` silently skips (data loss * dressed as data). I/O failures do not throw; they report through the * returned `warnings` (the accounting channel). */ export declare function appendSelectionManifest(input: SelectionManifestInput): SelectionManifestResult; export declare function senseSelectionManifest(input: SelectionManifestInput, onWarn?: (msg: string) => void): SelectionManifestResult; /** * Read all manifest rows, skipping schema-invalid lines (the generic-consumer * convention shared with `readLedgerEvents`; a measurement pass that must * count rejects reads the file raw — same division of labor as QBD's * compliance scanner). */ export declare function readSelectionManifests(totemDir: string, onWarn?: (msg: string) => void): SelectionManifestRow[]; //# sourceMappingURL=selection-manifest.d.ts.map