daemon: mark ADR 0013 fully accepted — PROTO landed the error-on-paused widening
PROTO closed the one remaining contract gap as contracts/ 1.3.0
(lane/proto commit 6db304a): event.task.state.error / TaskSummary.error
now populate on a paused transition CORE entered unilaterally, not just
on failed/retry_wait. Minor widening of an existing field's presence
condition, no retype, no new field, per contracts/README.md rule 4.
Updates every place in the ADR that referred to this as an open
question or unresolved gap: the status line, the pause-reason
bookkeeping in §2, the alternatives-considered pointer, and the
contract-gap section itself (renamed "surfaced, now closed"). Notes
1.3.0 is on lane/proto but not yet merged to main (still 1.1.0) —
daemon/src/sched/'s pause/resume logic should be written once that
merge lands, not before.
All four of ADR 0013's open items are now resolved: CORE confirmed
tasks_starved's structural exclusion and pause()/resume() idempotency
explicitly (verdict: "accept as written", not hedged), and adopted
"auto-pause" with no new wire term.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
# ADR 0013 — Task state-machine ownership: CORE, DAEMON, and the shared `paused`
|
||||
|
||||
**Status:** accepted by CORE, PROTO's open item 3 outstanding · **Date:** 2026-09-09
|
||||
**Status:** accepted by CORE and PROTO; all four open items resolved · **Date:** 2026-09-09
|
||||
· **Lane:** DAEMON, drafted at PROTO's request (D1 in `core/docs/proto-requests-m1.md`).
|
||||
**CORE's response:** `core/docs/adr-0013-core-response.md` (`lane/core`, commit `c65e664`)
|
||||
— accepted as written, no amendments, plus the pinned `tasks_starved` definition and
|
||||
`pause()`/`resume()` idempotency contract folded in below.
|
||||
**Remaining blocker:** PROTO landing the `error`-on-`paused` widening (open item 3) —
|
||||
`daemon/src/sched/`'s pause/resume logic is unblocked from CORE's side already, but not
|
||||
buildable correctness-preservingly until that lands.
|
||||
**PROTO's response:** the `error`-on-`paused` widening landed as `contracts/` **1.3.0**,
|
||||
`lane/proto` commit `6db304a` — no retype, no new field; only the presence condition on
|
||||
the existing `error: TaskError | null` field widens to include a daemon-initiated pause.
|
||||
Not yet merged to `main` (main is at 1.1.0 as of this writing) — `daemon/src/sched/`'s
|
||||
pause/resume logic should be written against `1.3.0` once that merge lands, not before.
|
||||
|
||||
## Context
|
||||
|
||||
@@ -95,10 +97,10 @@ The contract that makes this safe, per CORE's sign-off (`core/docs/adr-0013-core
|
||||
- **DAEMON persists *why* a task is paused**, in the `tasks` table, not in `TaskState`
|
||||
itself (the wire type stays a flat enum — this is DAEMON-local bookkeeping, not a
|
||||
contract change). A `pauseReason` distinguishing at least `user`, `schedule`,
|
||||
`queue_stopped`, `admission_reconcile`, and CORE's `error.code` when auto-paused. This
|
||||
is what makes §3's resume rule possible — but it needs a contract fix first: see "A
|
||||
contract gap this ADR surfaces" below. `error.code` is not currently carried on a
|
||||
transition into `paused` at all.
|
||||
`queue_stopped`, `admission_reconcile`, and CORE's `error.code` when auto-paused —
|
||||
now readable off `event.task.state.error` since PROTO's 1.3.0 widening (see "A
|
||||
contract gap this ADR surfaced, now closed" below). This is what makes §3's resume
|
||||
rule possible.
|
||||
- **CORE does not need to track why it's paused past the current occurrence.** Once
|
||||
paused, CORE's job is done; DAEMON is the only side that later decides whether to
|
||||
resume, and DAEMON is also the only side with persistent storage to remember the
|
||||
@@ -209,21 +211,25 @@ without CORE reporting them; forcing DAEMON to poll or reimplement that timing d
|
||||
etc.).** Rejected — it roughly doubles the enum for a fact only DAEMON's resume logic
|
||||
needs, and adding wire cardinality for internal bookkeeping is the kind of thing that
|
||||
becomes a compatibility problem the moment a client starts branching on it. The value
|
||||
DAEMON needs (CORE's reason) can travel on the existing `error` field instead — see the
|
||||
contract gap in open item 3 — without touching `TaskState` itself.
|
||||
DAEMON needs (CORE's reason) travels on the existing `error` field instead — see the
|
||||
closed contract gap below — without touching `TaskState` itself.
|
||||
|
||||
## A contract gap this ADR surfaces, not just an open question
|
||||
## A contract gap this ADR surfaced, now closed
|
||||
|
||||
`event.task.state.schema.json`'s own description scopes `error` to "whenever the new
|
||||
state is failed or retry_wait" — **not** `paused`. The one fixture
|
||||
(`event.task.state.json`) only exercises the `failed` case. So today, when CORE
|
||||
auto-pauses for `auth_required` or `server_file_changed`, DAEMON has no signal on the wire
|
||||
telling it *why* — §2/§3 of this ADR are unbuildable without one. This needs a PROTO
|
||||
follow-up (minor: widening an existing field's presence condition, per
|
||||
`contracts/README.md` rule 4 — no new field, no retype) to also populate `error` when
|
||||
`state == "paused"` and the pause was CORE-initiated. DAEMON is not asking for a way to
|
||||
tell CORE-paused from user-paused on the wire in general — `error: null` on a
|
||||
DAEMON-initiated pause is sufficient, since DAEMON already knows it just did that.
|
||||
`event.task.state.schema.json`'s description scoped `error` to "whenever the new state is
|
||||
failed or retry_wait" — **not** `paused`, at the time this ADR was drafted, and the one
|
||||
fixture (`event.task.state.json`) only exercised the `failed` case. So CORE auto-pausing
|
||||
for `auth_required` or `server_file_changed` had no wire signal telling DAEMON *why* — §2/
|
||||
§3 of this ADR were unbuildable without one.
|
||||
|
||||
**Closed by PROTO**, `contracts/` 1.3.0 (`lane/proto` commit `6db304a`, see the status
|
||||
line at top for the merge caveat): minor widening of the existing field's presence
|
||||
condition, per `contracts/README.md` rule 4 — no new field, no retype, `error` stays
|
||||
`TaskError | null`. It is now populated on a `paused` transition whenever CORE entered it
|
||||
unilaterally; `error: null` on a DAEMON-initiated pause is unchanged. New fixture
|
||||
`event.task.state.auto-paused.json` exercises the auto-pause case; the existing
|
||||
`event.task.state.json` fixture's stale "exactly when failed or retry_wait" assertion was
|
||||
corrected in the same change.
|
||||
|
||||
## Resolution of the four open items
|
||||
|
||||
@@ -236,10 +242,13 @@ DAEMON-initiated pause is sufficient, since DAEMON already knows it just did tha
|
||||
task (pause racing completion isn't an error), and no state-change event fires for a
|
||||
no-op — DAEMON reads resulting state from the callback/`TaskDetail`, never from the
|
||||
call's return value.
|
||||
3. **Still open — PROTO.** CORE's half is ready (`ErrorInfo` is populated on every
|
||||
auto-pause transition today's callback path would carry, using the existing B1
|
||||
taxonomy); the wire only needs `error` permitted when `state == "paused"`. This is the
|
||||
one remaining blocker on `daemon/src/sched/`'s pause/resume logic.
|
||||
3. **Landed — PROTO, `contracts/` 1.3.0** (`lane/proto` commit `6db304a`, not yet merged
|
||||
to `main`). `event.task.state.error` and `TaskSummary.error` are now populated "on
|
||||
every `failed` or `retry_wait` transition, and on a `paused` transition the daemon
|
||||
entered unilaterally" — a deliberate pause still carries `error: null`. New fixture
|
||||
`event.task.state.auto-paused.json` exercises an `auth_required` auto-pause directly.
|
||||
DAEMON's §3 resume rule can be implemented once `sched/` is built against `main` at
|
||||
1.3.0 or later.
|
||||
4. **CORE adopts "auto-pause".** No new wire or API term — the discriminator stays
|
||||
`state == paused` plus the presence of an `Error` (present ⇒ CORE-initiated, absent ⇒
|
||||
DAEMON-initiated), exactly as §2 already specified.
|
||||
|
||||
Reference in New Issue
Block a user