import { type ProviderRouting, type RandomBytesSource, type AppendLogEntryOptions } from "./artifacts.js"; /** Capabilities that journal (PRD AC3); the seam is capability-driven so T3 extends, not rewrites. Science verticals (T7): the science NOUN is one capability — the search/get distinction lives in the skeleton shape (result list vs single row), matching the search-vs-read precedent. */ export type JournalableCapability = "search" | "read" | "research" | "science"; /** One skeleton row: the identity of a result, never its content. */ export interface SkeletonItem { readonly url: string; readonly title: string; /** * Optional persistent identifiers (e.g. science works). * Note: contentHash is per-entry display context, never a cross-entry identity anchor, * so new entries hashing differently from legacy entries is acceptable. */ readonly identifiers?: { readonly doi?: string; readonly pmid?: string; readonly arxivId?: string; }; } /** Search skeleton (D2): url+title list of the result rows. */ export interface SearchSkeleton { readonly results: readonly SkeletonItem[]; } /** * Read skeleton (D2, T3): the single {url,title} identity of the fetch — * `finalUrl` when the Provider rewrote the URL, else the requested url. */ export interface ReadSkeleton { readonly results: readonly [SkeletonItem]; } /** * Research skeleton (D2, T3): the citations block — the url+title list * of the report's sources. */ export interface ResearchSkeleton { readonly results: readonly SkeletonItem[]; } /** The skeleton payload by capability (T3 completes the union). */ export type JournalSkeleton = SearchSkeleton | ReadSkeleton | ResearchSkeleton; /** The full journal entry (PRD AC2, ruling-locked field set). */ export interface JournalLogEntry { readonly kind: "journal"; readonly requestId: string; /** ms epoch — the CALLER's injected instant; never Date.now() in here. */ readonly timestamp: number; readonly capability: JournalableCapability; /** * Review must-fix 3: the ProviderRouting union — a single-provider run * records {mode:"single", effective, servedFrom}; a fan-out run * records {mode:"fanout", arms} faithfully (the same shape the save * hook logs). No silent skip of an always-on surface. */ readonly provider: ProviderRouting; /** Redacted query text (search) or URL (read/research). */ readonly query: string; /** sha256 hex of the normalized skeleton serialization. */ readonly contentHash: string; /** The response-cache key the miss was served fresh against. */ readonly cacheKey: string; readonly skeleton: JournalSkeleton; readonly tags?: readonly string[]; /** Cross-link to the --save entry when the same run saved (PRD AC10). */ readonly saveRef?: string; } /** * T2b repeat marker (PRD AC2 Variant B): the ~100B record of a * warm-cache re-ask. `repeatOf` is the requestId of the latest PRIOR * full journal entry sharing the same cacheKey (resolved through the * on-read map). Deliberately tiny — no query, no skeleton, no * contentHash, no cacheKey, no requestId of its own. */ export interface JournalRepeatMarker { readonly kind: "journal"; readonly timestamp: number; readonly capability: JournalableCapability; /** The serving provider, cache-honestly ({servedFrom:"cache"} etc). */ readonly provider: ProviderRouting; /** requestId of the referenced full journal entry. */ readonly repeatOf: string; /** Cross-link to the --save entry when the same run saved (PRD AC10). */ readonly saveRef?: string; } /** * Normalize a skeleton to the exact bytes contentHash is computed over * (and recall/export will compare later): recursively key-sorted JSON — * the buildProviderCacheKey request-hash idiom, so a differently-ordered * skeleton of the same rows hashes identically. */ export declare function normalizeSkeleton(skeleton: JournalSkeleton): unknown; /** * sha256 hex of the normalized skeleton serialization (the recall/export comparison anchor). * Note: contentHash is per-entry display context, never a cross-entry identity anchor. */ export declare function skeletonContentHash(skeleton: JournalSkeleton): string; /** * Search skeleton builder: the url+title identity of each result row. * Accepts the normalized search result rows (`FormattedResult` shape — * rank/title/url/summary) and keeps only url+title, in row order. * Science rows carry optional persistent identifiers threaded through to SkeletonItem. */ export declare function buildSearchSkeleton(results: readonly { readonly url?: string; readonly title?: string; readonly identifiers?: { readonly doi?: string; readonly pmid?: string; readonly arxivId?: string; }; }[]): SearchSkeleton; /** * Read skeleton builder (T3): the single {url,title} identity of the * fetch. Accepts the normalized reader envelope (content shape carries * url/finalUrl/title; extract shape carries url/finalUrl — title null * there renders the url as the row's title so the skeleton stays * self-contained). `finalUrl` wins when the Provider rewrote the URL. */ export declare function buildReadSkeleton(result: { readonly url?: string; readonly finalUrl?: string; readonly title?: string | null; }): ReadSkeleton; /** * Research skeleton builder (T3): the citations block — the url+title * list of the report's sources, in citation order. */ export declare function buildResearchSkeleton(sources: readonly { readonly url?: string; readonly title?: string; }[]): ResearchSkeleton; /** * Structural guard for one journal record (the widened T1 body check). * T2b splits the dispatch: a record carrying `repeatOf` is a REPEAT * MARKER (the tiny shape — exactly {kind, timestamp, capability, * provider, repeatOf}); anything else is a FULL entry. The presence of * `repeatOf` is the marker-vs-full discriminator. */ export declare function asJournalEntry(value: unknown): JournalLogEntry | JournalRepeatMarker | undefined; export declare function buildJournalCacheKeyMap(dir: string): Promise>; /** * T2b: build the tiny repeat marker for a warm-cache re-ask. The * provider is the cache-honest routing (servedFrom "cache" for single, * or the fanout arms) — the caller passes what the capture cell holds. */ export declare function buildJournalRepeatMarker(input: { readonly capability: JournalableCapability; readonly provider: ProviderRouting; readonly repeatOf: string; readonly saveRef?: string; readonly now: () => number; }): JournalRepeatMarker; export interface AppendJournalEntryOptions extends AppendLogEntryOptions { } /** * Append one journal entry under the artifacts write lock — the same * read-then-append critical section {@link appendLogEntry} runs (the * log IS index.json; the lock, the atomic replace, and the 0600 * discipline are inherited), plus a requestId collision remint: a * caller-minted id that already exists in the log gets a fresh 4-hex * tail. Strictly append-only: nothing here ever rewrites an existing * entry. * * Callers that know they are in the cache-hit path should use * {@link appendJournalEntryMaybeRepeat} instead: its read-check-append * is atomic under the write lock, so two concurrent cache hits never * both write a full entry under the same cacheKey. */ export declare function appendJournalEntry(dir: string, entry: JournalLogEntry | JournalRepeatMarker, options?: AppendJournalEntryOptions): Promise; /** * Swap a request id's 4-hex tail for a fresh mint (same timestamp * prefix — newRequestId already produced it from the caller's injected * clock, and re-minting the timestamp would defeat the same-second * collision check). Used under the log lock when an appended journal * entry collides with an existing requestId: `buildJournalRecall` * keys rows by requestId last-wins, so a colliding append would * orphan the earlier entry. */ export declare function remintRequestId(id: string, randomBytes?: RandomBytesSource): string; /** * Append under the write lock, but ATOMICALLY decide whether to write a * full entry or a tiny repeat marker based on whether the current log * already has a full entry whose cacheKey matches. * * The read-check-append runs as one critical section under the * artifacts-write lock — two concurrent cache hits after `history clear` * never both write a full entry under the same cacheKey (the check-then- * act race the plan calls out). * * The pre-built full entry is the "journal-cold" default. If the * cacheKey map resolves to a prior full entry, the marker builder is * called with that requestId and the result is appended instead. */ export declare function appendJournalEntryMaybeRepeat(dir: string, fullEntry: JournalLogEntry, makeMarker: (repeatOf: string) => JournalRepeatMarker, options?: AppendJournalEntryOptions): Promise; export interface JournalInput { readonly capability: JournalableCapability; readonly provider: ProviderRouting; readonly query: string; readonly cacheKey: string; readonly skeleton: JournalSkeleton; readonly now: () => number; /** Resolved secrets for the redaction pass (the invocation seam's cell). */ readonly secrets?: string[]; readonly tags?: readonly string[]; /** Set by the save hook when the same run saved (the saveRef cross-link). */ readonly saveRef?: string; } /** * Build one full journal entry from the serving facts: redact the query * text and the skeleton (url+title rows) through {@link redactSecrets}, * hash the NORMALIZED (unredacted-shape-preserving) skeleton, mint the * requestId from the injected clock. Redaction rewrites values only — * a redacted skeleton still passes the validator's shape check and * still serializes deterministically for the hash. */ export declare function buildJournalEntry(input: JournalInput): JournalLogEntry; /** * T4 (`history note`, DESIGN D5): the sentinel provider a hand-written * note carries. A note records work NO provider served, so the routing * must not read as a served run — `effective:"note"` is deliberately * outside the Provider registry ids, and `servedFrom` stays ABSENT * (neither "live" nor "cache": asserting a serve would claim a provider * call that never happened). The shape is NOT hand-choosable from the * CLI: the command exposes no `--provider`, and a smuggled real id is * mutation-pinned against. */ export declare const NOTE_PROVIDER_ROUTING: ProviderRouting; export interface NoteInput { readonly capability: JournalableCapability; /** Hand-written query text (search) or URL (read/research). */ readonly query: string; /** Hand-supplied skeleton rows (url+title pairs, in given order). */ readonly rows: readonly SkeletonItem[]; readonly now: () => number; readonly secrets?: string[]; readonly tags?: readonly string[]; } /** * T4: build one explicit journal entry from hand-supplied note fields * through the SAME write-seam discipline as {@link buildJournalEntry}: * redaction over query + skeleton rows, contentHash over the normalized * skeleton, requestId minted from the injected clock. The cacheKey is * the note's own namespace (a note references no response-cache * partition — `note:` prefix keeps it out of any cache-key collision * with real serving keys). */ export declare function buildNoteEntry(input: NoteInput): JournalLogEntry; /** One scored recall result row (the data-envelope identity set). */ export interface JournalRecallResult { readonly requestId: string; readonly timestamp: number; readonly capability: JournalableCapability; /** Query-token overlap count against the entry's corpus. */ readonly score: number; /** The newest ask: the entry timestamp, or a later repeat marker's. */ lastAsked: number; readonly saveRef?: string; readonly query: string; /** The skeleton rows the entry recorded (per-result context). */ readonly results: readonly SkeletonItem[]; } export interface JournalRecallOptions { /** Upper bound on entry timestamps (INCLUSIVE: ≤, boundary-pinned). */ readonly asOf?: number; readonly capability?: JournalableCapability; readonly limit?: number; } /** * Pure scoring over the log (DESIGN D4): tokenize the recall text, * score each FULL journal entry by query-token overlap against its * query + skeleton text, rank score DESC → recency DESC (lastAsked, * then requestId for full determinism). Repeat markers are NEVER * scored as separate results — they resolve to their referenced entry * and advance its `lastAsked` (only markers at/below `asOf` count). * Save entries are not text-searched (flags-only args); a save * surfaces only through its skeleton's `saveRef`. The log alone is * read: no master files, no cache, no network — ever. */ export declare function buildJournalRecall(log: readonly unknown[], text: string, options: JournalRecallOptions): JournalRecallResult[]; //# sourceMappingURL=journal.d.ts.map