SPEC.md

Scope

This document specifies Kron’s scheduling and execution semantics as a stable contract.

It applies to all Kron deployments and adapters:

  • kron-core (engine)

  • krond (host daemon)

  • kron-operator (Kubernetes controller)

Where an adapter cannot enforce a behavior directly, it must preserve the observable contract.


Terminology

  • Schedule: A cron expression interpreted in a timezone.

  • Timezone: IANA timezone used to interpret schedule and calendar constraints.

  • Nominal time: The scheduled timestamp produced by resolving a schedule for a period.

  • Period: The discrete scheduling opportunity anchored at one nominal time.

  • Period ID: Canonical identifier for a period.

  • Window: The allowed time interval in which an execution may be chosen.

  • Chosen time: The specific timestamp selected within the window for a period.

  • Decision: The computed record containing period, window, distribution, seed, and chosen time.

  • Constraint: A rule restricting which timestamps are allowed.

  • Candidate: A sampled timestamp within the window prior to constraint validation.

  • Deadline: Maximum allowed lateness relative to chosen time to still execute.

  • Execution: One realized run of a job (process spawn or Kubernetes Job creation).

  • Handled period: A period for which Kron has reached a terminal outcome.

  • Terminal outcome: executed, skipped, missed, or unschedulable.

  • Active execution: An execution that has started but not completed.

  • Identity: Stable identifier of a schedule entry.


Time Model

  • All internal comparisons use UTC instants.

  • Input schedules and constraints are interpreted in the configured timezone.

  • All persisted timestamps are stored as RFC3339 UTC.

  • Kron’s decisions are defined at second-level granularity for chosen timestamps unless a platform supports higher precision; implementations may use higher precision but must not violate determinism for the same inputs.


Inputs

A job definition provides:

  • identity: stable identifier

  • schedule: cron expression

  • timezone: optional, default UTC

  • window_mode: after or around

  • window_duration: non-negative duration

  • distribution: name + parameters

  • seed_strategy: name + optional parameters

  • salt: optional string

  • constraints: optional only and/or avoid

  • policy:

    • concurrency: allow|forbid|replace

    • deadline: duration (default 0s)

    • suspend: boolean (default false)

Adapters also provide:

  • now: current time instant


Outputs

For each job, Kron produces:

  • A Decision for a specific Period:

    • period_id

    • nominal_time

    • window_start

    • window_end

    • chosen_time

    • timezone

    • distribution + parameters

    • seed_strategy + parameters

    • seed_hash

    • constraints_applied summary

  • A terminal outcome per period:

    • executed

    • skipped

    • missed

    • unschedulable


Periods

Period definition

A period is the interval of responsibility anchored at one resolved nominal time.

For a schedule S, timezone TZ, and an evaluation time t, define:

  • prev_nominal(t): greatest nominal time ≤ t

  • next_nominal(t): smallest nominal time > t

A period is identified by its nominal time.

Period ID

period_id is the RFC3339 UTC representation of the nominal time.

Canonical form:

period_id = nominal_time_utc_rfc3339

Implementations must treat the same nominal instant as the same period even if represented in different timezones.


Window Semantics

Given nominal time N and window duration D:

  • If window_mode=after:

    • window_start = N

    • window_end = N + D

  • If window_mode=around:

    • window_start = N - (D / 2)

    • window_end = N + (D / 2)

window_start and window_end are inclusive bounds for candidate generation, with the constraint that the chosen time must satisfy:

window_start ≤ chosen_time ≤ window_end

If D=0, then:

chosen_time = N

Distribution Semantics

A distribution maps a seeded pseudorandom stream to a time within the window.

Distributions must be bounded: no value outside the window may be chosen.

Distributions may require parameters; missing parameters imply deterministic defaults.

Distribution evaluation must be deterministic for identical inputs.

The chosen time must be stable for:

  • the same identity

  • the same period_id

  • the same schedule configuration

  • the same seed strategy and salt


Seed Semantics

Determinism requirement

Kron must produce the same chosen_time for the same job definition and period, independent of:

  • process restarts

  • leader changes

  • reconciliation frequency

Seed inputs

Seed derivation uses:

  • identity

  • period_key (derived from period_id and seed strategy)

  • salt (string, possibly empty)

Seed strategies

Seed strategies define the period_key:

  • stable: period_key = period_id

  • daily: period_key = YYYY-MM-DD in TZ corresponding to nominal time

  • weekly: period_key = ISO week (YYYY-Www) in TZ corresponding to nominal time

Seed hash

Kron computes:

seed_hash = HASH(identity || "\n" || period_key || "\n" || salt)

Where:

  • HASH is a stable algorithm selected by Kron (cryptographic hash required)

  • identity, period_key, and salt are UTF-8 strings

  • || indicates concatenation

seed_hash is the canonical seed representation exposed in logs and status.

The pseudorandom generator uses seed_hash as seed material in a stable, specified transformation.


Constraint Semantics

Constraints restrict allowable chosen times.

  • only defines the allowed set.

  • avoid defines the disallowed set.

  • If both are present, a time is valid only if it satisfies only and does not satisfy avoid.

Constraints are evaluated in the schedule timezone.

Constraints apply to candidate timestamps during decision computation.

Candidate selection with constraints

To compute chosen_time:

  1. Sample a candidate within the window using the distribution.

  2. If candidate violates constraints, reject and sample a new candidate.

  3. Continue until a valid candidate is found or the sampling budget is exhausted.

Sampling budget is deterministic and fixed by Kron.

Unschedulable periods

If no valid candidate is found within the sampling budget, the period outcome is unschedulable and no execution occurs.


Decision Computation

For each job and period:

  1. Resolve nominal_time for the period based on schedule and timezone.

  2. Compute window bounds.

  3. Compute period_key based on seed strategy.

  4. Compute seed_hash.

  5. Initialize deterministic pseudorandom generator from seed_hash.

  6. Sample candidate(s) according to distribution within window.

  7. Apply constraints.

  8. Produce Decision including chosen time and metadata.

A Decision is specific to one period.

If the job definition changes, decisions for future periods may change. Decisions for already-handled periods must not cause additional execution.


Handling and Outcomes

A period is handled exactly once by reaching one terminal outcome.

Terminal outcomes:

  • executed: an execution was started for the period

  • skipped: execution was intentionally not started due to policy

  • missed: execution was not started because the deadline expired

  • unschedulable: no valid time could be selected within the window and constraints

Once a period is handled, Kron must not start another execution for that period.


Execution Trigger Semantics

A period becomes eligible for trigger when:

now ≥ chosen_time

If policy.suspend=true, no trigger occurs and no period is handled while suspended.

When eligible, Kron attempts to reach a terminal outcome by evaluating policies and system state.


Deadline Semantics

Let:

  • C be chosen_time

  • DL be policy.deadline

  • now current time at trigger evaluation

If DL=0s, then the period is handled as missed if now > C.

If DL>0s, then:

  • If now C + DL, execution may proceed

  • If now > C + DL, the period is handled as missed


Concurrency Semantics

Concurrency policy is evaluated at trigger time.

Let active indicate whether there is an active execution for the job identity.

  • allow: proceed to execute regardless of active.

  • forbid: if active=true, handle period as skipped.

  • replace: if active=true, terminate the active execution and proceed to execute.

Termination semantics for replace are adapter-defined but must be best-effort and observable.


Idempotency Semantics

Kron must guarantee at most one execution per (identity, period_id).

Adapters must implement an idempotency check before starting execution.

  • In Kubernetes mode: by checking for an existing owned Job with the period identifier.

  • In daemon mode: by consulting persisted state and verifying active/existing executions.

If an execution for the period is already recorded as started or completed, the period is handled and must not be executed again.


State Machine

Per job identity, each period transitions:

  • Planned: decision computed, chosen time known

  • Eligible: now ≥ chosen_time

  • Terminal: one of executed|skipped|missed|unschedulable

Execution sub-states for executed:

  • Running: started, not yet completed

  • Completed: finished with an exit result (adapter-specific)

State transitions are monotonic.

A period cannot transition from a terminal outcome to a different terminal outcome.


Spec Change Semantics

A job definition change is any change to:

  • schedule

  • timezone

  • window mode/duration

  • distribution or parameters

  • constraints

  • seed strategy or salt

  • policy settings

Effects:

  • Future decisions may change starting from the first unhandled period.

  • Already-handled periods must remain handled and must not execute again.

  • Active executions are governed by concurrency policy and adapter behavior.


Clock Change Semantics

Kron treats now as authoritative input.

  • If now jumps forward, some periods may become immediately eligible and will be handled subject to deadline and idempotency.

  • If now jumps backward, Kron must not re-handle already handled periods.

Adapters must persist handled period outcomes to preserve idempotency across clock changes.


Observability Contract

For each period and identity, Kron must expose:

  • period_id

  • nominal_time

  • chosen_time

  • terminal outcome

  • reason for non-execution (skipped, missed, unschedulable) when applicable

  • seed hash and distribution metadata sufficient to reproduce the decision

Logs must contain enough information to reproduce the decision off-line.


Invariants

Kron guarantees:

  1. chosen_time is within [window_start, window_end].

  2. At most one execution per (identity, period_id).

  3. Deterministic chosen_time for the same inputs and period.

  4. No unbounded historical replay.

  5. Terminal outcomes are monotonic and permanent for a period.

  6. Suspension prevents executions without advancing handled periods.

  7. If constraints make selection impossible, the period becomes unschedulable and no execution occurs.


Compatibility

  • The meaning of period_id, window computation, seed hashing inputs, and determinism rules are stable contracts.

  • New distributions, parameters, and constraint types may be added.

  • Any behavior change that affects decisions for the same inputs requires a major version bump of the relevant API surface.