/** * activity-source — where the Brain reads the activity ledger from (#1847). * * `.eddie-brain/activity.json` is the durable record: committed by the weekly * flush, bundled into the hosted function's graph at build time. That made the * HOSTED Brain blind between releases — the flush lands on `develop`, the * function is built from `main`, so `eddie_get_activity` on ds.bradfrost.com * answered "no activity" for days after a beacon had been captured, flushed * and merged (the reporter loop's first real beacon, 2026-09-04, needed an * out-of-cadence 0.62.1 purely to become visible). * * The fix keeps the file as the record and adds a hot read path: the flush * publishes the ledger it holds as a SNAPSHOT blob in the same Netlify Blobs * store the pending beacons live in — on every run, pending beacons or not, * so a failed publish heals on the next run. Readers that are handed a backend * prefer that snapshot whenever it is newer than the file. Readers that are * not (a laptop, CI, the stdio server on a live repo) read the file and open * nothing. * * Every answer says what happened: `source` (which ledger won), * `storeConsulted` (a backend was supplied), `storeReachable` (the snapshot * read did not throw) and `snapshotFound` (a parseable snapshot existed). The * deploy-preview check asserts the first two on a deployed function, which is * the only way a dead live path turns red instead of quietly answering from * disk (a tie between file and snapshot is indistinguishable from "never * looked" without them). * * The snapshot key sits outside the `report/` prefix so `ReportStore` never * mistakes it for a pending beacon. */ import { ActivityLedger } from './activity-ledger.js'; import type { BlobBackend } from './report-store.js'; /** Blob key of the published ledger snapshot. Outside ReportStore's `report/` prefix on purpose. */ export declare const ACTIVITY_SNAPSHOT_KEY = "snapshot/activity.json"; /** Which store the ledger a reader is holding came from. */ export type ActivitySourceKind = 'blobs' | 'disk'; export interface ActivitySource { ledger: ActivityLedger; source: ActivitySourceKind; /** The ledger's own `updatedAt`; null for an empty/missing ledger. */ updatedAt: string | null; /** A blob backend was supplied, so the snapshot was looked for. */ storeConsulted: boolean; /** The snapshot read completed without throwing (false when no backend, or the store errored). */ storeReachable: boolean; /** A parseable snapshot existed in the store. */ snapshotFound: boolean; /** When both stores were readable, the one NOT chosen and its `updatedAt` — for diagnostics. */ superseded?: { source: ActivitySourceKind; updatedAt: string | null; }; } /** Publish a ledger as the snapshot blob. Called by the flush right after it writes the file. */ export declare function publishActivitySnapshot(backend: BlobBackend, ledger: ActivityLedger): Promise; export interface SnapshotRead { ledger: ActivityLedger | null; /** The read itself succeeded (the blob may still have been absent or unparseable). */ reachable: boolean; } /** Read the snapshot blob back as a ledger. Never throws; says whether the store answered. */ export declare function readActivitySnapshot(backend: BlobBackend): Promise; /** * Pick the fresher of the two ledgers. ISO-8601 timestamps compare lexically, * so string comparison is a correct ordering. A ledger with nothing in it never * wins over one with data, whatever its stamp says. */ export declare function chooseActivitySource(disk: ActivityLedger, blobs: ActivityLedger | null): ActivitySource; export interface LoadActivityOptions { /** * The blob backend to consult, or null/undefined to read disk only. Callers * decide the policy (the hosted function passes one; local callers pass * nothing), so this module never opens a store on its own. */ backend?: BlobBackend | null; /** Injectable clock and cache TTL for tests. */ now?: () => number; ttlMs?: number; } /** Drop the per-process cache (tests, and after a write). */ export declare function resetActivitySourceCache(): void; /** * Load the ledger from disk and, when a backend is supplied, from the snapshot * blob too, returning whichever is fresher. With a backend the whole answer * (disk parse included) is cached per process for a short TTL so a warm * function does not re-read either store on every tool call. Read-only: this * never writes to the store or the file. */ export declare function loadActivity(activityPath: string, options?: LoadActivityOptions): Promise; //# sourceMappingURL=activity-source.d.ts.map