The Scheduler Contract

The Scheduler Contract

This document specifies the universal scheduler contract: the operations a scheduler must offer, the lifecycle by which a composition uses one, how capabilities and versions are negotiated, the guarantees an implementation must uphold, and the points at which the contract may be extended. It is the companion to ARCHITECTURE.md, which explains why the boundary is drawn where it is.

Normative keywords (MUST / MUST NOT / SHOULD / MAY) are used in the sense of FORGE-100 Part 2.


1. The parties


2. Self-description: identity, version, capabilities, manifest

Before any observation, a scheduler declares itself through a SchedulerManifest, composed of:

Manifest invariants (MUST):


3. Capabilities and the operations they gate

A capability names one thing a scheduler can be asked to produce. The ratified vocabulary (SchedulerCapability) and the operation each unlocks:

Capability Operation Returns
job-discovery discover_jobs() tuple[Job, ...]
queue-discovery discover_queues() tuple[Queue, ...]
node-discovery discover_nodes() tuple[Node, ...]
account-discovery discover_accounts() tuple[Account, ...]
allocation-discovery discover_allocations() tuple[Allocation, ...]
reservation-discovery discover_reservations() tuple[Reservation, ...]
software-discovery discover_software() tuple[SoftwarePackage, ...]
storage-discovery discover_storage() tuple[StorageResource, ...]
telemetry-discovery discover_telemetry() tuple[Metric, ...]
event-observation observe_events(window) tuple[SchedulerEvent, ...]
snapshots snapshot() SchedulerSnapshot
incremental-updates incremental_update(basis) IncrementalUpdate

Rules:

CapabilitySet deduplicates and canonically orders its members, so two sets with the same capabilities are equal and serialize identically regardless of construction order.


4. Observation shapes

Every operation returns domain objects, either directly (the discovery operations) or wrapped in an observation shape:

Snapshots carry state; telemetry (discover_telemetry) is separate because measures are observations about objects; events are separate because history is not state. This mirrors the object / measure / event triad of FORGE-002.


5. Version negotiation

The contract is versioned major.minor, additive-by-default:

negotiate_contract_version(name, required=…, offered=…) returns the agreed version (the required one) when the offer satisfies it, and raises VersionNegotiationError — carrying both rendered versions — when it does not. There is no silent downgrade. The version this package defines is CONTRACT_VERSION (currently 1.0).


6. Capability negotiation

negotiate_capabilities(name, offered, required=…, desired=…):

Negotiation is a pure function performed before any observation, so a composition learns whether a scheduler can serve it without touching reality.


7. Compliance and deterministic behaviour

verify_contract_compliance(scheduler) returns a deterministic tuple of ContractViolation (empty means compliant). It is static: it reads the manifest and inspects which operations are overridden, and never invokes an observation operation, so it runs anywhere without any external system. It verifies:

Deterministic-behaviour requirements on implementations (MUST):


8. Lifecycle

A composition uses a scheduler through a fixed lifecycle. No step performs a write to reality — a scheduler only ever observes.

 DECLARE     the scheduler exposes its manifest (identity, versions,
    │        capabilities). Pure declaration; nothing is observed.
    ▼
 VERIFY      verify_contract_compliance(scheduler) is empty. A
    │        non-compliant scheduler is refused before use.
    ▼
 NEGOTIATE   negotiate_contract_version(...) and negotiate_capabilities(...)
    │        agree, or fail loudly. The consumer now knows exactly what it
    │        may ask for.
    ▼
 CHECK       health() reports observability, timestamped. A DEGRADED or
    │        UNAVAILABLE report states what is wrong.
    ▼
 OBSERVE     the granted operations are invoked — discovery, events,
    │        snapshot, incremental_update — each returning domain objects.
    │        Repeated as the composition's cadence requires.
    ▼
 (steady state: re-check health, re-observe; a broken source surfaces as an
  honest health report and an UnsupportedOperation is never silently swallowed)

9. Extension points

The contract grows by amendment, never by local invention (a capability only one deployment understands is a leak, not a capability). The sanctioned extensions:

Each extension is a contract minor bump recorded against this package. A breaking change (removing or re-typing anything) is a major bump and a constitutional act. Instances and adapters never extend the contract; they implement or consume the version they pin.