needs-ruling — the pending-human-decision flag (epic)
#50
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#50
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
needs-ruling— the pending-human-decision flagAccepted from discussion #30 (opened 2026-07-22 by triage, ruled by @danmt in-thread). This epic carries the decisions so the two child issues stay narrow, and so nobody reopens a settled question by reading only one of them.
The gap
The doctrine names a human ruling twice — TRIAGE.md's escalate outcome sends the decision to a discussion, REVIEWER.md ends a panel deadlock with "the builder escalates to the maintainer for a ruling" — but nothing on the board marks where a ruling is pending. A stalled disagreement is indistinguishable from work in progress, and the human has no queue of their-turn items beyond mergeable PRs.
state:needs-humancannot serve: it means exactly "this PR could be merged right now", and that narrowness is load-bearing — the retiredstate:needs-rebaseis the family's proof that a label meaning two things lies about both. It is also PR-only, and rulings are needed on issues.Decisions (settled — do not reopen in a child)
needs-ruling, notblocker:ruling.blocker:*is PR-only vocabulary and every existing row names something the builder owes; a ruling is owed by the human, and this label must live on issues too.D4C5F9— the light shade ofstate:needs-human's8250DF, so the human axis reads as one family, exactly asblocker:unrequested(E99695) is the light shade of theB60205blockers.needs-rulingis a state, not a signal. It stays set until the decision is reached, however many exchanges that takes. A human comment does not clear it; agreement does.ready/claimed/blocked, or the PR back to its state machine. Never a side effect of the label disappearing.claimed/blocked— it is a fact about what is in the way, likeblocker:*, and is not a queue label (LABELS.md's one-of-three invariant must keep ignoring it). On PRs the reconciler treats it blocker-ish:needs-rulingandstate:needs-humanare mutually exclusive by construction, not by convention.merge-next's set side andrelease— the reconciler reads it and never writes it. Automation never guesses whether a human has decided.needs-rulingitemstale— waiting on a human is legitimately quiet, same treatment asblocked. But it does nudge at a longer horizon: comment-only, 7 days, resetting on any activity in the thread, so an active back-and-forth is never nudged and only genuine silence is. Never flips a label.The 2026-07-23 amendment, ruled (D11–D16)
Added 2026-07-23 14:52Z by triage, closing out the 13:04Z escalation. The amendment (10:43Z) proposed the first two; the third is @danmt's ruling; D14–D16 are its mechanical consequences, triage's to state and triage's to be wrong about. Same standing as D1–D10: settled — do not reopen in a child.
paths-ignoremay skip docs in PR checks, with no dissenting reviewer anywhere.<details>. Fields: a one-line decision header,Options:(exhaustive, mutually exclusive, more than three means the question is not ready),Recommend:(mandatory — a flag without one moves the whole problem to the human, which is the thing being fixed),Blocked:(what stops and what continues),Default:(D13). The failure it fixes is literal: #16's escalation was correct and drew "I got lost in that wall of text."Default: <X> at <UTC>and proceeds on it at the deadline, saying so out loud; anything else waits. At 12h: do not fire a stale default — re-read it against what has landed and ask both questions again; new doubt makes it a hard block, which is a legitimate outcome. At 24h: the work proceeds regardless, as a PR stating which way it went and what doubt stands — nothing is merged by this, so a wrong pick costs a review and the alternative costs indefinite silence. Past 24h: triage picks, records the pick as a decision, and stays accountable; the operator overturns at merge like everything else. The operator is notified throughout.labeledevent — not on last activity, not on theDefault:deadline. A re-flag starts a fresh ladder. The 24h and past-24h rungs apply whateverDefault:says, hard blocks included:Default:governs only whether the builder may act before the PR stage. An active back-and-forth still climbs — a thread arguing for 24h without agreement is exactly the thread that should produce a concrete PR to argue against. This is the one clock that does not reset on activity; D10's 7-day nudge still does, and is unchanged.needs-ruling. The sweep may only comment — the escalation comment's shape is checked by literal field-label presence (never its prose, D4), and each rung produces one board comment per episode so the fleet's board polls can see it. A ladder nobody watches is a hard block with extra steps.needs-ruling.notify.shwatches open PRs carryingstate:needs-humanand nothing else, so no poller reads this flag at all — three escalations spent their whole lives invisible to the operator on 2026-07-23 (#16's, #56's, and this epic's own 13:04Z flag, which surfaced only because a human happened to look). One tracked message per item, edited in place as it crosses the rungs, removed when the flag clears — a live queue, not a feed. The box-side script is the operator's, so this is a spec on paper until an operator acts, exactly as FLEET.md's reviewer request trigger was.Constraints the children must preserve
state:*answers whose ball is it on this artifact;needs-rulinganswers what is in the way. The one place they touch is D8's exclusion — copied from the existing rule atdecide_stateL285-L293, not invented beside it.state:addressing— see child 1), not a sixth row.Task list
2f0d3c6)needs-rulingexists on heavy-duty/ceremony. Done 2026-07-23 11:48Z — run 30004512442, green;gh label listnow carriesneeds-ruling|D4C5F9, so the flag is applyable on the live board. Why it could not happen inside #51's PR, kept for the record: the reconcile job checks out the default branch by design, so no pre-merge dispatch can see the new row, and neither builder nor triage identities hold dispatch or label-create rights (triage holdstriagepermission only,push: false). The two escalations that argued for it: #16's fork-PR-workflows escalation ran its entire course invisible to the board — raised 01:23Z, ruled 09:24Z (option (a), fork-PR workflows enabled with both token toggles off), closed out 09:29Z; and #56's D7 escalation, raised 10:46Z and still unruled. Consequence discharged: triage appliedneeds-rulingto #56 at 12:03Z, saying so there. #56 is the flag's first live wearer on this board.dfcfd45), closed 12:41Z. One implementation for both surfaces inlib/ruling.sh, wired into both reconcilers; dogfooded against fixtures #65/#66, both since closed.Default:), escalated 13:04Z and ruled by @danmt at 14:24Z/14:42Z. Flag cleared and the three children below minted 2026-07-23 14:52Z.f6797d0), closed 15:15Z. Both dependents flippedblocked→readyby themselves at 15:15:36Z: the sweep parsedBlocked by #72and resolved it. The hand-flip caveat this line used to carry was wrong — it applies only to cross-repo declarations, which the parser deliberately leaves to triage (#61).lib/ruling.sh: the comment's shape checked mechanically, and the rungs commented on the board (D12, D14, D15). Landed 2026-07-23 in #78 (cb3d482), closed 16:05Z with all three panel verdicts approving heada4918a5. Dogfooded live against fixture #79, closed by triage once its evidence was captured, per #18's fixture rule.30a818d, commitac6e980), closed 16:35Z with all three panel verdicts approving headac6e980. Verified by triage againstmainrather than taken from the PR: FLEET.md carries the notifier'sneeds-rulingqueue — one tracked message per item, edited in place across the four rungs, removed on clearance, alerting on a passedDefault:or a flag standing past 24h — and triage's past-24h wake condition. Box-side, so it is a spec on paper until an operator editsnotify.sh, exactly as D16 says.#51 landed 2026-07-23 in #54 (
2f0d3c6), #18 the same day in #32 (66f1c08), #52 the same day in #64 (dfcfd45), #72 in #75 (f6797d0), #73 in #78 (cb3d482) and #74 in #80 (30a818d). The amendment that kept this epic open is ruled, minted as #72–#74, and all three have landed: every child is closed and every criterion below is met, so this epic is closed. Updated by triage 2026-07-23 17:03Z, superseding the 16:00Z revision of the same day.Definition of done
needs-rulingexists in the bootstrap taxonomy and is documented in LABELS.md, TRIAGE.md, BUILDER.md and REVIEWER.md. — done in #51 (ded7f9a,bd215b0): the row is inlabels-reconcile.sh(needs-ruling|D4C5F9|…) and all four role files carry it. In-tree only — the row is in the taxonomy the bootstrap writes, but the label does not exist on this repo until the dispatch above runs; that is the task-list item, not this one.needs-rulingcan never carrystate:needs-human, proven by a contract test, and is never markedstale. — done in #51 (bd215b0thedecide_stateexclusion + the staleness skip,6db4558the contract tests: exclusion-not-latch, adds-nothing-to-blockers(), sweep-proof, stale-exempt).needs-rulingkeeps its queue label, is never markedstale, is nudged at 7 days of silence, and is flagged if it carries no escalation comment. — done in #52 (dfcfd45):lib/ruling.shholds the decisions, both reconcilers call them. Verified onmainrather than taken from the PR:test/ruling.test.sh+test/issueflow-reconcile.test.shpin "claimed plus a pending ruling is a healthy issue" (invariant 1 still ignores the flag), "a 10-day-quiet claim under a ruling is not reclaimed", "an applied stale comes off under a pending ruling", "the same silence still nudges the pending ruling" + "...and the nudge went to the decider with the escalation linked", and the bare-flag pass with its 15-minute window, per-episode marker and one-comment-per-episode guarantee.f6797d0).lib/ruling.shstill touches a label. — #73, landed in #78 (cb3d482). Verified onmainat87f2432:ruling_shape_decisionreturnsMALFORMEDwith the missing field labels named,RULING_RUNG12_MARKER/RULING_RUNG24_MARKERscope one comment per rung per episode (a re-flag climbs its own ladder, a rung first observed past 24h pages once and not retroactively), andlib/ruling.shcontains no label mutation at all — pinned bytest/ruling.test.sh's "the ruling sweep never wrote a label" and "no add/remove-label mutation names the ruling flag".needs-rulingquery as one tracked message per item, edited across the rungs, and triage's past-24h wake condition. — #74, landed in #80 (30a818d). Verified onmainat87f2432(FLEET.md L67–L115 and L49–L51); box-side execution stays the operator's, per D16 and FLEET.md's own on-paper caveat.needs-ruling. — done in #52 (dfcfd45), proven on both surfaces against recording stubs: "no issue edit across every probe names the ruling flag (#50 D9)" on the issue side, "...and the sweep never touches needs-ruling itself" plus a no-edit assertion on the PR side. The #51 half (decide_staterefusesstate:needs-humanwhile the flag stands, and it is an exclusion not a latch) is pinned intest/labels-reconcile.test.sh. Whole suite green ondfcfd45: 15 test files, 0 failed.Definition of done refreshed by triage 2026-07-23 17:03Z, superseding the 16:05Z revision that still had two criteria open: #73 landed in #78 (16:05Z) and #74 in #80 (16:35Z), so all seven criteria are met. Checked against
mainat87f2432, not against the PRs — whole suite green there, 15 test files, 0 failed (test/ruling.test.shalone: 92 passed, 0 failed).@danmt — #51 merged (#54,
2f0d3c6), so the one item on this epic that is not a builder's to take is now live. Checklist ticked.The post-merge dispatch is ready to run, and nothing else will run it. I verified the bootstrap is dispatch-gated rather than merely dispatch-preferred:
labels.ymlpassesbootstrap: ${{ github.event_name == 'workflow_dispatch' && 'yes' || 'no' }}to both reconcile steps, andbootstrap_labels()is the only caller ofgh label create --force. So the*/15cron ticks — which do now check out amaincarrying theneeds-rulingrow — will sweep state and never create the label. Waiting does not fix this; a dispatch does.Confirmed just now on the live repo:
needs-rulingis still absent fromgh label list. After the run,gh label list -R heavy-duty/ceremony | grep needs-rulingshould showneeds-ruling A human decision is pending — question, options and a recommendation are in the comment #D4C5F9. It is idempotent (--force), so re-running costs nothing.Neither builder nor triage identities can do this:
gh workflow runreturns 403 (Must have admin rights) andgh label createreturns 404 for both — we holdtriage, which applies labels but does not create them. That is the roster's one-box-per-credential design working as intended, not a gap to close; option (c) from #51 — granting builder identities write — stays declined.Until the run lands, the flag is inert but silent: the reconciler only ever reads
needs-ruling(D9), so the missing-label warnings at L458-L463 never fire for it and the machine degrades correctly. The only casualty is that four role files document a flag nobody can apply. This epic's DoD (needs-rulingexists in the bootstrap taxonomy) is not satisfied by the merged row alone — it needs this run.No action needed from anyone else; #52 stays
blockedon #18.Triage, board hygiene: epic body updated, no label or state changes. Two things had gone stale against main since #51 merged (#54,
2f0d3c6).The dependency line under the task list still read "#51 is unblocked and
ready. #52 isblockedby #18 … and by #51". #51 is closed and shipped, so it is nobody's dependency now. The line now says #52 is blocked on #18 alone — which is what #52's own body already said, so the epic was the only place left disagreeing with the board.DoD row 2 checked — "a PR carrying
needs-rulingcan never carrystate:needs-human, proven by a contract test, and is never markedstale." Verified against main rather than taken from the PR description:bd215b0carries both halves (thedecide_stateexclusion at L310 and the staleness skip at L522), and6db4558carries the tests — exclusion-not-latch,needs-rulingadds nothing toblockers(), the sweep never strips the flag, stale-exempt and un-stale.What is deliberately still unchecked, so nobody reads the two edits as more progress than they are:
gh label listreturns 23 labels andneeds-rulingis not among them. It is inert, exactly as that item predicts.The live case named in the dispatch item is unchanged: #16's fork-PR-workflows escalation to @danmt still cannot be flagged, and the thread is ~1.5h old, far inside D10's 7-day nudge horizon.
Rest of the board this pass: no
blocked→readyflips are due (#14/#15 wait on #13, whose rig PR is open; #52 waits on #18, whose PR #32 is open), and no claims are reclaimable — #13, #16 and #18 all have an open PR and activity within the last two hours. #36 stays as it is: it is #18's[fixture #18]stale-claim fixture, recorded as such so a later scan does not reclaim the thing being tested.Triage, epic hygiene — two corrections to the body, no scope change.
1. The task list's "live case waiting on the dispatch" is stale, and I am the one who wrote it. #16's fork-PR-workflows escalation was ruled by @danmt at 09:24Z — option (a), enable, both token toggles off — and closed out at 09:29Z with the ruling folded into #16's body. Nothing is blocked on the dispatch now, so the item no longer carries urgency it does not have.
What the episode did produce is a clean datum for this epic: the escalation was raised at 01:23Z and ruled at 09:24Z, and for those eight hours the board showed #16 as
claimedand nothing else — indistinguishable from a builder mid-build. That is the exact gap this epic's opening names, observed live rather than argued from. The item stays open and the reasoning is stronger, not weaker.2. Definition-of-done item 1 is checked, with the split stated out loud.
needs-ruling|D4C5F9|…is in the taxonomy the bootstrap writes (labels-reconcile.shL389) and all four role files document it, both landed in #51. The label still does not exist on this repo —gh label listconfirms — but that is the dispatch task-list item, not this DoD line, and leaving the line unchecked while its sibling reads "done in #51" made #51 look half-landed when it is not.Rest of the board scanned, nothing else moved: #52 stays
blockedon #18 alone (PR #32 is still an open draft, so the sweep it extends is not onmain); every other queue label checks out against its facts.Amendment for the
needs-rulingepic (#50) — two things #16 taught that thecurrent spec does not carry.
1. The trigger is an unowned decision, not a disagreement.
#16 did not stall on ambiguity or on a review dispute. It stalled on two
questions the builder had no authority to answer: which org owns the published
package namespace, and whether
paths-ignoremay skip docs in PR checks. Bothare policy. A builder that answers them is guessing; a builder that waits
without flagging is silently blocked. So the flag fires whenever a decision
belongs to a human — org policy, published artifacts, secrets, prod, or any
choice whose cost lands outside the PR — and not only when reviewers disagree.
2. The comment has a fixed shape, and it is short.
The whole thread on #16 was correct and it still failed, because the reply it
got was "I got lost in that wall of text." Analysis is not the deliverable; a
decidable question is. Above the fold, at most five lines:
Rules for the fields:
three, the question is not ready to be asked.
problem to the human, which is the thing being fixed.
build; say so, or the flag reads as a full stop.
needs-rulingbecoming a new stall class. Areversible decision — one that can be changed inside the PR before merge —
gets a stated default and a UTC deadline, and the builder proceeds on it when
the deadline passes, saying out loud that it did. An irreversible one —
published artifacts, secrets, prod, org policy — gets
none — hard block, andwaits. Of #16's two questions:
paths-ignorewas reversible, the packagenamespace was not.
Suggested default window: 12 hours. Long enough to cross one sleep, short enough
that a missed ruling costs a morning rather than a day.
3. Consequences already in flight, worth pinning here.
The label suppresses the staleness sweep while it is set (#52 covers this) —
an issue waiting on a human is not an abandoned claim, and must not be reaped
as one. When the ruling lands, whoever applies it removes the label in the same
comment that records the decision, so the flag's lifetime matches the wait
rather than outliving it. And a
needs-rulingolder than its stated deadline isitself a fleet-health signal — that is the thing worth alerting on, not the
flag's existence.
🧭 needs-ruling — may a builder proceed on a stated default when a ruling is pending, or does every ruling hard-block?
Options: A — reversible decisions carry
Default: <X> at <UTC>and the builder proceeds on it when the deadline passes, saying so out loud; irreversible ones (published artifacts, secrets, prod, org policy) getnone — hard block. B — everyneeds-rulinghard-blocks; no defaults, no deadlines.Recommend: A, with a 12-hour window — it crosses one sleep, and #16 is the evidence: the
paths-ignorehalf of that escalation was reversible inside the PR and still cost 8 hours of dead time waiting beside the half that genuinely could not move.Blocked: only the amendment's child issue. Every minted child of this epic is closed and the machinery is live; #56, #68 and #70 continue untouched.
Default: none — hard block. This one is org policy about how much autonomy the fleet has, so by its own rule A it does not get a default.
Why this is on the board and not just in a comment
The amendment filed at 10:43Z proposes two things this epic's decisions do not carry: the flag fires on any decision a human owns (not only reviewer disagreement), and the escalation comment has a fixed short shape — the one above is written in it, deliberately, since "I got lost in that wall of text" was the failure it exists to fix.
Everything in it except
Default:is mintable as written: the trigger definition is a doctrine edit to TRIAGE.md and BUILDER.md, and the comment shape is a template plus, at most, a mechanical check the bare-flag pass already has the machinery for.Default:is not. It decides whether an agent may act on an unruled policy question, and no wording of the child issue can leave that open — a builder reading it would have to guess, which is the failure the issue contract exists to prevent. So the child is not minted yet, and this epic stays open with the amendment as its one outstanding item rather than being closed as complete.Both options are cheap to implement. The cost is not in the code; it is that A is a standing grant of autonomy and B is a standing stall risk, and neither is mine to hand out.
Ruling: option A — a
needs-rulingmay carry a default, but only when thecall is clear. If there is reasonable doubt, hard-block. The bar is
affirmative: a default requires the builder to be confident the decision is
reversible, not merely unable to think of a reason it isn't. Unsure is not a
tie, it is a block.
What follows is a ladder, not a single deadline. A ruling never stalls forever,
and the authority to unstick it escalates rather than evaporating.
0–12 hours. A clear, reversible decision carries
Default: <X> at <UTC>andproceeds when the deadline passes, saying so out loud. Anything with reasonable
doubt — and everything touching published artifacts, secrets, prod, or org
policy — carries
none — hard blockand waits.At 12 hours. Do not fire a stale default. Re-read it against what has landed
since it was written and ask both questions again: does this default still
hold, and is there still reasonable doubt? A default written twelve hours ago
may have been overtaken — the base moved, a sibling PR landed, the option set
changed. If it still holds and doubt is gone, proceed. If doubt has appeared,
it becomes a hard block; that is a legitimate outcome, not a regression.
At 24 hours, the work proceeds regardless — as a PR. Even with reasonable
doubt standing, the builder picks the option, opens the PR, and states in the
body which way it went and what the unresolved doubt was. The reasoning: an
open PR embodying a concrete choice is a far better prompt for a decision than
a question in a comment thread. Nothing is merged by this — the human still
gates the merge — so the cost of a wrong pick is a review, and the cost of the
alternative is indefinite silence.
Past 24 hours, triage has the last word. If a
needs-rulingis stillstanding and doubt remains, triage picks the option the builder proceeds on. The
decision escalates from builder to triage rather than dissolving into nobody's
hands. Triage records the pick as a decision and remains accountable for it —
and the operator can overturn it at merge, which is the same gate that governs
everything else here.
The operator is notified throughout. A
needs-rulingthat is aging isexactly the state a human needs to see, and the whole point of the ladder is
that time passing changes what happens.
One implementation consequence, flagged rather than ruled — this is my
observation, @danmt did not rule on it:
Nothing currently notifies on
needs-ruling. The operator notifier(
notify.shon the triage box) watches open PRs carryingstate:needs-humanand nothing else;
needs-rulinglives on issues, and no poller reads it.So as written, the notification clause of this ruling does not happen. That is
not hypothetical: three escalations spent their lives invisible to the operator
today — this epic's own 13:04Z flag, #56's R1–R3, and #16's — each raised
correctly and each surfaced only because a human happened to look.
The ladder makes this worse, not neutral, because every rung is time-based: 12h,
24h, and "past 24h" are all moments that must actually be observed by
something. A ladder nobody watches is a hard block with extra steps.
The box-side script is the operator's, so this is a spec and not a claim of
done: the notifier gains open issues labelled
needs-ruling, with the messageedited in place as the item crosses 12h and 24h — the same one-message-per-item
tracking it already does for
state:needs-human, so an aging ruling reads as alive queue rather than a feed. Worth its own issue under this epic.
Bruv the ideas was to file the issue @dan-claude-bot
Ruling recorded, flag cleared, children minted. Closing out my own escalation per D6/D7.
The decision (@danmt's, 14:24Z / 14:42Z): option A — a
needs-rulingmay carry a default, but only when the call is clear. The bar is affirmative: confident the decision is reversible, not merely unable to think of a reason it isn't. Unsure is a block. Then the ladder: 0–12h a clear reversible decision proceeds on its stated default at the deadline, out loud; at 12h the default is re-read against what has landed, and new doubt makes it a hard block; at 24h the work proceeds regardless as a PR stating which way it went and what doubt stands, merged by nobody; past 24h triage picks, records the pick, and stays accountable, with the operator overturning at merge. The operator is notified throughout.Carried into the epic body as D11–D16, alongside the two amendment items the ruling ratified (the trigger is any decision a human owns; the escalation comment has a fixed short shape). D14–D16 are mine, not @danmt's: the rungs anchor on the current episode's
labeledevent and apply whateverDefault:says; the machine observes the rungs and never fires them (D9 intact); the notifier is where "notified throughout" actually happens. If any of those three read the ruling wrong, say so and the flag goes back up.Minted:
ready— a builder can take it now.lib/ruling.sh: the comment's shape checked by literal field labels, and one board comment per rung per episode.blockedby #72, because it greps for #72's strings.blockedby #72, because two files inventing two vocabularies for one ladder is the drift this epic exists to prevent.Flow:
needs-rulingcomes off this epic now. The epic stays open on the three children — its DoD gained one row each. It is not a queue item; nothing else about its labels changes.One thing the ruling exposes and #74 only half fixes: every rung is a moment something must observe, and the observers are a
*/15cron on the board (#73) and a box-side script no agent here can edit (#74). Until an operator makes that change, the notification clause is doctrine on paper — #74 says so in the file rather than pretending otherwise.Closing: every child has landed and every criterion is met. Checked against
mainat87f2432, not against the PRs.The two that closed since the last revision:
cb3d482), merged 16:05Z, three approvals on heada4918a5.lib/ruling.shnames the missing fields on a malformed escalation, fires one comment per rung per episode (RULING_RUNG12_MARKER/RULING_RUNG24_MARKER; a re-flag climbs its own ladder, a rung first seen past 24h pages once and not retroactively), and mutates no label anywhere —test/ruling.test.shpins both halves, 92 passed / 0 failed. Whole suite green: 15 test files, 0 failed.30a818d), merged 16:35Z, three approvals on headac6e980. FLEET.md carries the notifier'sneeds-rulingqueue — one tracked message per item, edited in place across the four rungs, removed on clearance, alerting on a passedDefault:or a flag standing past 24h — and triage's past-24h wake condition.What is deliberately not held open. D16's notifier is box-side, so FLEET.md specifying it is the deliverable; the
notify.shedit is the operator's and the file says so. Nothing else on this epic waits on anything — that is what closing it asserts, and if the ladder misbehaves in the wild it comes back as an issue againstlib/ruling.sh, not as this epic reopened.D1–D16 remain the settled record for
needs-ruling; read them here, not in the children. Closed by triage 2026-07-23 17:03Z.claude-bot-andresmgsl referenced this issue from heavy-duty/stoke2026-08-30 15:07:58 +00:00