Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.43.0.
You will create a booking policy for a registry counter, read what it publishes, and prove with a fixture what callers get when one of its two stations closes. Everything runs offline: no database, no token issuer, no network.
Install schedulingctl
Section titled “Install schedulingctl”schedulingctl is the authoring and operator tool for
Registry Scheduling. The installer published with the latest release installs that release,
checks each download against the release’s SHA256SUMS, and puts the binaries in ~/.local/bin;
keep that directory on your PATH. On Linux amd64 it also installs the scheduling runtime, which this tutorial does
not use.
curl -fsSL https://github.com/registrystack/registry-stack/releases/latest/download/scheduling-install.sh | bashschedulingctl --versionschedulingctl <version>Create the project
Section titled “Create the project”Create a project from the exact-time template:
schedulingctl init ./scheduling --template standalone-exact-time --format json{ "command": "init", "created": [ "scheduling.yaml", "runtime.example.yaml", "records.yaml", "fixtures/counter-stations.yaml", "fixtures/fold-day-rebooking.yaml" ], "template": "standalone-exact-time"}scheduling.yaml is the policy: what callers may book and when. records.yaml holds the
locations and staff stations a running deployment loads separately, and the two fixtures prove
the policy offline. You meet runtime.example.yaml only when you deploy.
Check the policy
Section titled “Check the policy”schedulingctl check ./scheduling --format json{ "offerings": { "count": 2, "ids": [ "registry-update-30", "fold-day-update-30" ] }, "projectId": "registry-updates"}The report’s effective section also prints a policyDigest, which changes whenever the booking
behaviour changes, while databaseAccess and networkAccess are both false: the check touched neither. An
offering is one thing a caller can book. Ask
schedulingctl what the first one means:
schedulingctl explain ./scheduling --format json{ "exactTime": { "bufferAfterMinutes": 5, "bufferBeforeMinutes": 5, "durationMinutes": 30, "horizonDays": 60, "leadTimeMinutes": 120, "maximumRecipients": 1, "pool": "update-stations", "startIncrementMinutes": 30 }, "id": "registry-update-30", "location": "bangkok-counter", "mode": "exact-time"}registry-update-30 is a 30-minute appointment at bangkok-counter, starting on the half hour,
bookable from two hours to 60 days ahead, with five minutes kept free on each side. Each booking
takes one station from update-stations, a resource pool
of interchangeable stations. The policy names the pool; records.yaml lists its members:
- id: update-stations members: - resourceId: station-1 available: true - resourceId: station-2 available: trueRun the fixtures
Section titled “Run the fixtures”A fixture states a moment in time, the pools as they stand then, and booking requests with the answer each must get. Run both:
schedulingctl test ./scheduling --format json{ "cases": [ { "detail": "admitted onto station-1 for 1 unit(s)", "name": "first-caller-takes-a-station", "status": "pass" }, { "detail": "refused booking.duplicate-active (detailed: booking.duplicate-active)", "name": "same-key-retry-is-refused", "status": "pass" }, { "detail": "admitted onto station-2 for 1 unit(s)", "name": "second-caller-gets-the-other-station", "status": "pass" }, { "detail": "refused capacity.exhausted (detailed: capacity.exhausted)", "name": "both-stations-busy-refuses-another-caller", "status": "pass" } ], "file": "fixtures/counter-stations.yaml", "status": "passed"}The first caller asks for 09:00 Bangkok time (02:00 UTC) and takes station-1. The same caller
asking for 09:30 is refused, because the offering allows one active booking per subject. A second
caller takes station-2 at 09:00, and a third is refused because both stations are taken. Each
case’s duplicateKey names the subject, not the idempotency key: a retry under the same
idempotency key returns the first answer instead.
Close a station
Section titled “Close a station”Suppose station-2 is out of service that morning. Say so in the fixture’s facts:
- resourceId: station-2 available: true available: falseRun the fixtures again:
schedulingctl test ./scheduling --format json{ "detail": "expected admitted onto station-2 for 1 unit(s), actual refused capacity.exhausted (detailed: capacity.exhausted)", "name": "second-caller-gets-the-other-station", "status": "fail"}The command exits with status 1. With one station open, the second caller is refused like the third. That is the behaviour you want, so the case expectation is what is wrong. Correct it:
expect: type: admitted units: 1 resource: station-2 type: refused code: capacity.exhaustedschedulingctl test ./scheduling --format json{ "ok": true, "status": "passed", "proofBoundary": "offline-synthetic"}offline-synthetic marks the limit of this proof: the fixtures exercise the policy against facts
you wrote, not against a running deployment’s ledger.
How Registry Scheduling works follows a booking through the running service: the hold a caller takes, the appointment it confirms, and the grant that allows both.