Found during review of #1092.
What is unchecked
SchedulingPolicy::check_window_records is the canonical combined validation of a
policy against the published window records it governs. Among other rules,
check_window refuses a window whose staffing.pool also backs an exact-time
offering, with PolicyCheckReason::SharedSupplyUnpartitioned. The comment there
states the reason plainly: the two modes count the same staffing differently, so
the mix would double-book it in a ledger that cannot see the conflict.
That validation runs only inside registry-schedulingctl (records::validate
and the project checks), and it runs against the policy file on the operator's
disk, offline, before any transaction opens.
Neither database write re-checks it:
PostgresStore::apply_policy locks every supply anchor FOR UPDATE and fences
offerings that carry active claims, but it never reads scheduling_windows
and never calls check_window_records.
replace_facts swaps the window records and their anchors without consulting
the deployed policy document.
- The runtime performs no facts validation at startup;
crates/registry-scheduling/src/runtime.rs
calls apply_policy directly.
Why it matters
Windows are deployed under a policy that has no exact-time offering on pool A, so
the authoring check passes. The policy is later changed to add an exact-time
offering backed by pool A. Restarting the runtime publishes that policy: the
active-claim fence does not object, because the window offering keeps its
service, location, mode, and supply.
The two offerings then sell the same staffing through independent anchor rows. In
lock_and_snapshot, an exact-time admission locks exact.pool and an arrival
admission locks window.id. They are different rows in scheduling_supply, so
the capacity transactions never serialize against each other, and the ledger has
no way to observe that the window's staffing and the pool are the same people.
This is exactly the configuration check_window refuses.
Reaching it requires an operator to deploy a policy their own authoring tool would
refuse, so it is not caller-reachable. It is still a gap in the invariant the
product states it owns absolutely.
Same family as #1242
#1242 covers the supply-identifier collision between a window id and a pool id,
which records::validate refuses in the CLI and the store does not. This issue is
the same shape: a combined policy-by-records invariant enforced only in
CLI-local, file-based validation and never re-checked at either database write.
They probably want one fix.
Direction
Re-enforce the combined invariants inside both transactions, since both already
hold the supply anchors locked: load the deployed window records inside
apply_policy and refuse a candidate policy that conflicts with them, and load
the deployed policy inside replace_facts and refuse conflicting records. A fix
to only one side leaves the other direction open.
Until that lands, consider recording the gap in
products/scheduling/contracts/security-invariant-matrix.yaml as a deferred
entry whose compensating control is the authoring check, so the security contract
is not silent about it. No current row over-claims this, so the matrix is accurate
today, only quiet.
Found during review of #1092.
What is unchecked
SchedulingPolicy::check_window_recordsis the canonical combined validation of apolicy against the published window records it governs. Among other rules,
check_windowrefuses a window whosestaffing.poolalso backs an exact-timeoffering, with
PolicyCheckReason::SharedSupplyUnpartitioned. The comment therestates the reason plainly: the two modes count the same staffing differently, so
the mix would double-book it in a ledger that cannot see the conflict.
That validation runs only inside
registry-schedulingctl(records::validateand the
projectchecks), and it runs against the policy file on the operator'sdisk, offline, before any transaction opens.
Neither database write re-checks it:
PostgresStore::apply_policylocks every supply anchorFOR UPDATEand fencesofferings that carry active claims, but it never reads
scheduling_windowsand never calls
check_window_records.replace_factsswaps the window records and their anchors without consultingthe deployed policy document.
crates/registry-scheduling/src/runtime.rscalls
apply_policydirectly.Why it matters
Windows are deployed under a policy that has no exact-time offering on pool A, so
the authoring check passes. The policy is later changed to add an exact-time
offering backed by pool A. Restarting the runtime publishes that policy: the
active-claim fence does not object, because the window offering keeps its
service, location, mode, and supply.
The two offerings then sell the same staffing through independent anchor rows. In
lock_and_snapshot, an exact-time admission locksexact.pooland an arrivaladmission locks
window.id. They are different rows inscheduling_supply, sothe capacity transactions never serialize against each other, and the ledger has
no way to observe that the window's staffing and the pool are the same people.
This is exactly the configuration
check_windowrefuses.Reaching it requires an operator to deploy a policy their own authoring tool would
refuse, so it is not caller-reachable. It is still a gap in the invariant the
product states it owns absolutely.
Same family as #1242
#1242 covers the supply-identifier collision between a window id and a pool id,
which
records::validaterefuses in the CLI and the store does not. This issue isthe same shape: a combined policy-by-records invariant enforced only in
CLI-local, file-based validation and never re-checked at either database write.
They probably want one fix.
Direction
Re-enforce the combined invariants inside both transactions, since both already
hold the supply anchors locked: load the deployed window records inside
apply_policyand refuse a candidate policy that conflicts with them, and loadthe deployed policy inside
replace_factsand refuse conflicting records. A fixto only one side leaves the other direction open.
Until that lands, consider recording the gap in
products/scheduling/contracts/security-invariant-matrix.yamlas adeferredentry whose compensating control is the authoring check, so the security contract
is not silent about it. No current row over-claims this, so the matrix is accurate
today, only quiet.