L1 source registry, drift classification, and result contract#
Decision#
Lookout L1 is a source registry plus a non-throwing drift-check runner over Forage snapshots. The decisions that shape the observable contract:
Versioned JSON registry. Sources live in a
{ version: 1, sources: [...] }document (default<cwd>/lookout.sources.json, overridable by library path or CLI--registry). No YAML dependency. EachLookoutSourcecarriesid,kind(web-page|api-record|structured-file), an absolute HTTP(S)url, and acadenceHint. Web/API sources carry a TraverseTargetFieldSchema[]plusrenderPolicy; structured files instead require aformatdiscriminator (yaml|json|csv) and retain raw bytes for downstream parsing. Validation reports every deterministic issue at once with index/id context and never touches the network.Four result kinds. A check classifies into exactly one of
unchanged-304,unchanged-hash,changed, orerror. There is no fifth "initial" variant: the first successful observation ischangedwithchangeBasis: "initial"and a null prior ref, which keeps the union closed while staying honest that nothing preceded it.L1 owns comparison and persistence. Forage's
fetchSourceonly reads the store and computes a freshbodyHash; it never persists a fresh 200 and never compares hashes. L1's exact order per source is: load the prior snapshot →fetchSource({ ..., revalidate: true }, { store })→ validate the dependency result → classify → persist the fresh snapshot → emit. A304re-serves the prior snapshot markednotModified/fromCacheand is not re-persisted (persisting it would fabricate a fresh capture;checkedAtbelongs to the result, not a rewritten snapshot).unchanged-hashandchangedcome from L1's own sha256bodyHashcomparison of prior vs. fresh.Store before emit. A successful
changed/unchanged-hashresult is only returned after the requiredstore.putresolves, so every emitted provenance ref is replayable. A byte-identical repeat of the prior capture (same URL, status, body, text decoding, redirects, render state, andetag/last-modified) is not put again: itsunchanged-hashresult names the already-stored prior capture as both refs, so the refs stay replayable and stable sources do not grow history (kontourai/lookout#87). Changed captures are bounded by opt-in retention through Forage'sprune: the newestkeepLast, the latest, the result's prior, and every caller-cited snapshot are kept. When the cited set cannot be read, nothing is pruned. The same bytes under a charset that decodes differently are a new capture (kontourai/lookout#89).Logical snapshot refs, never paths. Provenance uses Forage's
buildSnapshotSourceRef(unchanged-304carries one ref; fresh comparisons carry both prior and current refs). Paths are never exposed. Exact replay delegates to Forage'sresolveSnapshotSourceRef, which authenticates the referenced body and durable replay metadata without fetching.The runner never throws (R2). Operational failures become typed results, not exceptions: a Forage
FetchErroris preserved with its discriminant underorigin: "forage"; a rejected store read/write, a throwing injected fetch, a malformed dependency result, or any unexpected exception becomes anorigin: "lookout"error (prior-read|persistence|history-full|dependency-contract|unexpected).checkAllruns sources sequentially, returns one ordered result per source, and continues past errors.JSONL CLI, external scheduling.
lookout check <id>andlookout check --allemit exactly one compact JSON object per checked source per stdout line, in registry order; human/fatal diagnostics go to stderr. Exit is0whenever the requested checks were attempted (including per-sourceerrorresults); non-zero only when an invocation/registry failure prevents producing the requested result set. Cadence/scheduling stays external (boo).Inert render policy.
renderPolicyis validated registry data for web/API sources; structured files do not carry it. L1 never translates it (orkind) into a fetch/render policy and never adds retries/robots/redirects — those remain Forage's. Render wiring remains deferred.Dependency gates. Exact replay requires
@kontourai/forage >= 0.4.0, the first release containing canonical durable-reference resolution. Forage also owns validator-scoped revalidation for trustworthyunchanged-304results. Lookout exact-pins@kontourai/traverse@0.14.1separately for its schema and extraction-proposal contracts.
Boundary#
L1 stores targetSchema but performs no extraction, projection, semantic
diffing, event emission, notification, crawling, retention, or review policy —
those are later slices. A Datum provider-resolver capability is threaded for L2
but never invoked in L1 (resolving would materialize secrets for no purpose).