/** * The connector's bounded result journal for SPEC §13.19. * * This is NOT a public event log, and it is not the consent or revocation record family: those * live in the space auth store and are the host's. This holds only what a consented holder may * later look up, for as long as §13.19 lets it, and answers the closed `tool-result` states. * * Two rules here are the ones that leak if they are written loosely, so they are enforced in the * types rather than left to a caller: * * 1. **A RESULT never creates an entry** (§13.19.2). Only a START does. A result that arrives for * an unknown call is dropped and said so, because an entry minted by a RESULT would carry no * frontier decision and therefore no eligible consent set — it would be a result nobody * established a right to read, sitting in a store whose whole purpose is that right. * 2. **The eligible consent set is fixed at START and may never grow** (§13.19.3). `start` takes * it once; nothing else writes it. Growth is the shape of the bug where a consent granted * after a result was captured reaches back and reads it, which §13.19.3 forbids in the same * sentence that forbids a newly consented holder reading before its own frontier. * * Revocation runs the other way and is allowed to shrink any set, including to nothing, at which * point the entry is deleted rather than kept unreadable: a retained entry whose audience is empty * is bytes nobody can read, so holding them is cost without purpose. * * The frontier itself (§13.19.2's durable position `P`) is decided before `start` is called and is * not this type's business; it arrives already resolved as the consent ids the caller computed as * eligible. Keeping placement out of here is deliberate — it is the emitter's position algebra, * and mixing it with storage is how a position comparison ends up being made against wall-clock * capture time, which §13.19.2 explicitly forbids. */ import { type ToolResultAnswer } from "@cotal-ai/core"; export interface ToolResultJournalOptions { /** Injectable clock. Retention is the only time input; capture order never decides eligibility. */ now?: () => number; maxEntries?: number; maxContentBytes?: number; retentionMs?: number; } export interface ToolResultStart { threadId: string; toolCallId: string; /** The fixed eligible set, decided at START against the frontier. Empty means nobody, so no entry. */ consentIds: readonly string[]; } export interface ToolResultCapture { threadId: string; toolCallId: string; content: string; error: boolean; } /** Why a capture did not land. `no-entry` is the §13.19.2 refusal, not an error condition. */ export type ToolResultCaptureOutcome = "captured" | "no-entry" | "already-captured"; export declare class ToolResultJournal { #private; constructor(options?: ToolResultJournalOptions); /** Entries currently held, after dropping anything past retention. Bounded by `maxEntries`. */ size(): number; /** * Open an entry for a call the frontier admitted. A second START for the same call does NOT * reopen or re-audience it: the set is fixed at the first one, and a repeat is the connector * saying the same thing twice rather than a new decision. */ start(o: ToolResultStart): boolean; /** Record the result of a started call. Never creates an entry (§13.19.2). */ capture(o: ToolResultCapture): ToolResultCaptureOutcome; /** * Drop `consentId` from every entry, deleting any entry it empties. Answers how many entries * were deleted, which is what a caller reports; the count of entries merely narrowed is not * interesting, because narrowing changes no reader's answer except that holder's. */ revoke(consentId: string): number; /** * Answer a lookup for a holder presenting `consentIds`, which the caller has already checked are * active, unexpired and unrevoked for that holder — this type does not know holders and must not * guess at one. Eligibility is set intersection and nothing else. * * `not-shared` and `missing` are deliberately distinct: `missing` says no such call was recorded, * `not-shared` says it was and this holder is not in its audience. Collapsing them would be * friendlier to a caller and would also tell an unconsented reader which tool calls exist. */ read(o: { threadId: string; toolCallId: string; consentIds: readonly string[]; }): ToolResultAnswer; } //# sourceMappingURL=tool-result-journal.d.ts.map