Ephemeris Context#
Ephemeris is the Kontour suite's external time actor. Its role is to turn time into a trigger — across two schedule axes. The deadline engine ingests Flow-emitted run-output TrustBundles, reads each claim's Hachure freshness fields (expiresAt / ttlSeconds), arms durable wake-ups for the derived deadlines, and nudges Flow at the deadline. The recurring-schedule core is a pure evaluation engine (cron / one-shot at / fixed every, timezone- and DST-aware, with catch-up) that consumers like Station build on instead of growing their own clock.
Layer Model#
- Hachure owns the shape: TrustBundle and claim schemas, including freshness fields.
- Surface owns the meaning: derives
fresh/stalefromexpiresAt. - Flow owns the reaction: re-derives at the real
nowonevaluateRunand emits artifacts. - Ephemeris owns the time, on two axes: arms wake-ups from data-derived deadlines and triggers Flow after they pass; and evaluates recurring wall-clock schedules as a pure core (it computes when, never fires — the consumer owns firing).
Invariants#
- It triggers, it never authors. Ephemeris writes nothing to TrustBundles or ledgers. A fire is only a nudge; Flow re-derives and Surface decides for real.
- Expiry is derived; invalidation is an event. Ephemeris reacts to
expiresAt; it does not synthesizestale,revoked, or other trust state. - Owns no trust or process authority. Its durable state is private wake-up bookkeeping, not authoritative product state.
- Deterministic timing belongs behind
Clock. Tests useManualClock; avoid real sleeps or real wall-clock waits in deterministic paths.
Vocabulary#
- TrustBundle: Flow-emitted bundle that Ephemeris consumes as a read model. Hachure forbids a bundle-level
id; Ephemeris keys bundles onsource. - Claim: Freshness-bearing item inside a TrustBundle. Claims with no freshness field arm no deadline.
- Deadline /
fireAt: Data-derived instant fromexpiresAtorttlSeconds; not a cron schedule. - Source: Adapter that feeds bundles to
arm()and removals tocancel(). Current sources includeDirectoryWatcherSource,RegistrySource, and the raw programmaticarm()API. - Store: Ephemeris-owned durability for pending and fired wake-ups.
JsonFileStoreis the simple default,AppendLogStoreis append-per-mutation with replay and compaction, andInMemoryStoreis for tests. - Trigger: Nudge path that fires at a deadline. The default
FlowEvaluateTriggercalls Flow'sevaluateRun(runId)through an injectableFlowRunner; trigger contracts returnvoid. - Coalescing: Per-claim collapse of redundant pending deadlines so a flappy claim cannot storm.
- Rate limiting:
minFireIntervalMslimits fires for the same claim; under-firing inside the window is safe because Flow re-derives at the next allowed fire. - Schedule: A recurring plan —
cron(Vixie 5-field + optional IANA timezone, DST-aware), one-shotat(a fixed epoch instant), or fixedevery(an interval anchored to the job's own run history). Discriminated bykind. - ScheduledJob: The minimal input the schedule evaluator reads —
schedule,lastRunMs,createdMs,enabled. Ephemeris computes; the consumer owns the full job record (id, owner, payload, persistence). - RunRecord: Durable per-fire record (
firedAtMs,scheduledForMs,missedCount,durationMs,success,outputRef). A type only — Ephemeris does not store these; the consumer persists them. Intended receipt substrate for station#1889.
Boundary#
Ephemeris is an external time actor, not an orchestrator or trust authority. It may discover emitted bundles, persist its own wake-up state, and call Flow when deadlines pass. It must not create, edit, or reinterpret trust artifacts, and it must not move scheduling authority back into Surface or Flow.
The recurring-schedule core is pure and narrower still: it evaluates when a schedule fires (nextOccurrence / isOverdue / missedCount) and returns the answer. It does not fire, does not persist, and owns no job lifecycle — the consumer does all of that. nowMs is always an argument, never read from the wall clock, so the core is deterministic without a Clock shim.