CORE reviewed and accepted (core/docs/adr-0011-core-response.md, lane/core@7bf5cb5), with three amendments folded in: - yield (slot transfer at the next segment boundary) as the mechanism that satisfies min-1-before-seconds out of a full budget; steal stays slot-neutral as originally written. - "admission implies progress" is bounded-delay (min(next yield boundary, low_speed_secs) + connect_timeout), not immediate — widens the starvation-assertion window from 2s to ~low_speed_secs + connect_timeout (45s). - starved_tasks()/starved_since(TaskId) added to the accessor set; segments_active() and tasks_starved definitions pinned (a 'connecting' segment counts as held, not starved). All five open questions answered (min-1 buildable without inversion, probe pool size 4 outside the budget, drain-not-kill live-apply, ordered TaskId list for priority, 4Hz + starved-edge callback coalescing). Section 6 rewritten: connection.maxActiveSegments landed on the wire in PROTO's ADR 0012 while this was in flight, so the daemon-local stopgap is dropped. daemon/src/sched/ is unblocked. Both docs updated in the rebased vdm-daemon worktree against the frozen 1.0.0 contract. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
68 lines
3.6 KiB
Markdown
68 lines
3.6 KiB
Markdown
# DAEMON → CORE — the engine API `sched/` needs before it can be written
|
|
|
|
Status: **resolved**. CORE answered in full: `core/docs/adr-0011-core-response.md`
|
|
(`lane/core`, commit `7bf5cb5`). Kept as a record of what was asked and the shape of the
|
|
answer; the API itself is now specified in
|
|
`docs/adr/0011-admission-control-and-the-segment-budget.md` §"Engine API `sched/` is built
|
|
against". `sched/` may be written against it.
|
|
|
|
Ranking followed `contracts/README.md` rule 4 conventions even though this wasn't a
|
|
`contracts/` change: new API surface = cheap, land anytime; a behavioural promise (min-1
|
|
fairness) = needed CORE's explicit sign-off before DAEMON built on the assumption. That
|
|
sign-off is in.
|
|
|
|
---
|
|
|
|
## C1. Occupancy read-out, not inference — **resolved**
|
|
|
|
Requested `budget()`, `segments_active(TaskId)`, `on_budget_changed`. CORE's answer adds
|
|
`starved_tasks()` and `starved_since(TaskId)` (amendment A3) and pins two definitions:
|
|
`segments_active(id)` counts a segment in `connecting` state as held (it is progress, not
|
|
starvation), and `tasks_starved` counts only `segments_active == 0`. See ADR 0011 §3.6.
|
|
|
|
## C2. `set_max_active_segments(uint32_t)` live-apply — **resolved: drain, never kill**
|
|
|
|
Confirmed DAEMON's assumption. Lowering runs in-flight segments to their next boundary; no
|
|
new segment starts while over the new ceiling; nothing is aborted, no partial range lost.
|
|
If the new ceiling is below the running-task count, CORE honours min-1 for the top-priority
|
|
subset and reports the rest via `tasks_starved` — DAEMON's governor must reconcile and
|
|
pause the lowest-priority excess itself (CORE does not auto-pause). See ADR 0011 §2.
|
|
|
|
## C3. `set_host_segment_cap(host, uint32_t)` — **resolved, confirmed as proposed**
|
|
|
|
CORE keeps the `host → cap` map and derives a task's host from its URL/mirror set; DAEMON
|
|
owns the table and pushes it. See ADR 0011 §4.
|
|
|
|
## C4. Contract gap: `connection.maxActiveSegments` — **resolved by PROTO**
|
|
|
|
PROTO landed it (ADR 0012, `connection.maxActiveSegments` default 32,
|
|
`connection.maxTotalBufferBytes` default 128 MiB, `TaskDetail.effectiveBufferBytes`) while
|
|
this was in flight. No daemon-local stopgap needed — `sched/` reads the wire field
|
|
directly. See ADR 0011 §6.
|
|
|
|
## C5. Fairness rule sign-off — **resolved, with two amendments**
|
|
|
|
CORE confirmed min-1-before-seconds is implementable without a priority-inversion at slot
|
|
release (two-pass allocator: guarantee pass over zero-slot tasks in DAEMON's priority
|
|
order, then a growth pass; a released slot always re-enters the pool at pass 1, never
|
|
handed back locally). Two amendments to what DAEMON assumed:
|
|
|
|
- **A1** — "steal" (slot-neutral, unchanged) isn't the whole mechanism; **"yield"** is the
|
|
slot-transfer operation that actually satisfies min-1 out of a full budget: an
|
|
over-quota task releases one slot at its next segment boundary, bounded by that
|
|
segment's remaining bytes.
|
|
- **A2** — "admission implies progress" is **bounded-delay**, not immediate:
|
|
`time_to_first_slot ≤ min(next yield boundary, low_speed_secs) + connect_timeout`, not
|
|
"connect timeout + per-host cap" alone. DAEMON's starvation-invariant assertion window
|
|
widened from the originally proposed 2 s to `low_speed_secs + connect_timeout` (~45 s)
|
|
accordingly.
|
|
|
|
Priority order (open item 4) is an ordered `TaskId` list pushed via `set_task_order` on
|
|
change — not an integer, not per-tick. See ADR 0011 §3.
|
|
|
|
## C6. Probe pool sized outside the segment budget — **resolved, confirmed**
|
|
|
|
Dedicated pool, default size 4, `set_probe_pool_size(uint32_t)`, independent of
|
|
`maxActiveSegments`; probe cancellation is immediate. DAEMON still bounds probe
|
|
*submission* on its own side. See ADR 0011 §5.
|