/** * The minimal synchronous SQLite handle the store needs — structurally the same * surface the Urban runtime's DataLayer exposes (`host.openSqlite`). Kept local * so the store depends on a shape, not on the runtime package. */ export interface SqliteDb { /** Execute one or more statements with no result (DDL, migrations). */ exec(sql: string): void; /** Run a parameterised statement, returning the changed-row count. */ run(sql: string, params?: unknown[]): { changes: number; lastInsertRowid: number | bigint; }; /** Run a parameterised query, returning all rows as plain objects. */ all>(sql: string, params?: unknown[]): T[]; } /** A monotonic wall clock, injectable for deterministic tests. */ export interface Clock { now(): number; } /** The default clock: `Date.now()`. */ export declare const systemClock: Clock; /** The recognised blackboard entry kinds (verbatim from nano-workforce). */ export declare const BLACKBOARD_KINDS: readonly ["file-claim", "constraint-change", "scope-change", "learning", "note"]; export type BlackboardKind = (typeof BLACKBOARD_KINDS)[number]; /** Coerce an arbitrary `kind` to a known value, defaulting to "note". */ export declare function normalizeKind(kind: unknown): BlackboardKind; /** What a writer supplies to {@link BlackboardStore.append}. */ export interface BlackboardInput { /** The writing task's slug (or "system" for host writes). */ readonly authorTask?: string; /** The entry kind; unrecognised values normalise to "note". */ readonly kind?: unknown; /** Repo-relative paths this entry concerns (feeds `file-claim` conflicts). */ readonly files?: readonly string[]; /** The human/agent-readable note. Required and non-blank. */ readonly body: string; /** Optional wave index the writer was dispatched in. */ readonly wave?: number | null; /** Idempotency key; a repeat append under the same `scope` is a no-op. */ readonly dedupeKey?: string; } /** The parsed, agent-facing view of an entry (files decoded to an array). */ export interface BlackboardEntry { readonly id: number; readonly authorTask: string; readonly kind: string; readonly files: string[]; readonly body: string; readonly wave: number | null; readonly createdAt: string; } /** * An advisory conflict-of-intent: a sibling has already claimed a file this * writer is about to claim. Reported per (file, prior claim) so the later * claimer can back off, coordinate, or escalate. First-writer-wins is advisory * only — the blackboard NEVER locks; merge-time gates are the real safety net. */ export interface ClaimConflict { readonly file: string; readonly authorTask: string; readonly id: number; readonly body: string; readonly createdAt: string; } /** One incremental read: entries after `since` (write order) plus the `cursor`. */ export interface BlackboardPage { readonly entries: BlackboardEntry[]; /** * The board's current head id — the true head even when `since` filters every * entry out, so a caller that is fully caught up learns it is caught up * (cursor unchanged). `0` for an empty board. */ readonly cursor: number; } export interface BlackboardStoreOptions { /** Injectable clock for deterministic `created_at` in tests. Default {@link systemClock}. */ readonly clock?: Clock; } /** * True only for a UNIQUE / PRIMARY-KEY / duplicate violation — never a * foreign-key or other constraint failure. We match the *specific* violation * (extended SQLite codes, or the specific words) rather than the bare word * "constraint", so a `FOREIGN KEY constraint failed` (real corruption, not a * benign duplicate) is always rethrown rather than silently swallowed. */ export declare function isUniqueViolation(err: unknown): boolean; export declare class BlackboardStore { #private; constructor(db: SqliteDb, options?: BlackboardStoreOptions); /** * Apply the canonical blackboard DDL (idempotent). Callers that let the app * DataLayer migration runner apply `db/migrations/003_agentic_blackboard.sql` * do not need this — the family module calls it so the store is usable against * a bare source too. The DDL is identical to the migration (drift-guarded). */ ensureSchema(): void; /** * Append an entry to a board, idempotently. A blank `body` is rejected. When a * `dedupeKey` is supplied and an entry already exists for it under this * `scope`, the write is a no-op and the existing id is returned * (`inserted: false`) — so an engine job retry re-appending the same fact * never duplicates. */ append(scope: string, input: BlackboardInput): { inserted: boolean; id: number; }; /** * One incremental read of a board: entries with `id > since` in write order, * plus the board's current head `cursor`. An agent polling mid-flight passes * `cursor` back as the next `since`, so it pulls only what siblings added since * its last read. */ readPage(scope: string, opts?: { since?: number; }): BlackboardPage; /** A board's entries in write order (id asc). `since` returns only `id > since`. */ read(scope: string, opts?: { since?: number; }): BlackboardEntry[]; /** * Prior `file-claim` entries by OTHER authors under this `scope` that overlap * `files`. A writer's own earlier claim is never a conflict with itself. Pass * `beforeId` to restrict to strictly prior claims (`id < beforeId`) — the * family computes conflicts AFTER inserting its own claim and sets `beforeId` * to that new id, so first-writer-wins is decided by insertion order and a * sibling claim that raced in concurrently is still surfaced without matching * the writer's own just-written row. */ detectFileClaimConflicts(scope: string, opts: { authorTask?: string; files: readonly string[]; beforeId?: number; }): ClaimConflict[]; /** Number of entries under a scope. */ count(scope: string): number; }