amend(dn-supervision-and-liveness): A1 the power axis + graduate bp-154 — the scheduler learns to see the battery - #30
Merged
Conversation
…r learns to see the battery Warrant: issue #12 (type:direction, route:orchestrator), migrated from frozen finding-0279. THREE measured emergencies, not the two its title names — Jul 24 (drained to 1%; the embedder starved under critical-battery throttle, code_backfill timed out, code_sync WEDGED, the daemon died unwitnessed and stayed dead three days), Jul 28 (100%→8% in 2h40m, caught at the wire under KeepAlive), and Aug 1 (fatal — the machine died mid-run; run #39 came up in recovery). Battery hardware is healthy and stays healthy: re-measured 2026-08-05 at Condition Normal, 95% maximum capacity, 128 cycles. The drain is load, not degradation. The scheduler is the defect. §2.7 gave the supervisor exactly one resource axis to refuse on — memory (NN-8, "the scheduler refuses breaching work"). That principle is not memory-specific; it was simply never given a second axis. A1 grows the same dispatch-time refusal a POWER axis: remaining energy is a schedulable resource, and work that would spend the last of it is breaching work. This amends rather than founding a new note because it adds a DIMENSION to machinery this note already decided, not a subsystem. Grounded on the code, not asserted: A1.1 The shed mechanism already exists — HEAVY_TIERS (supervisor.py:59) shed by blocked_tiers() on presence.foreground_active() (:130-135), composed at THE ONE claim site (:177, pinned at :153). This adds a PREDICATE, not machinery. Load-bearing pin: power_blocked_tiers() is its OWN predicate, never folded into blocked_tiers() — quoting the code's own reasoning (:131-134), "two different reasons to refuse a tier, conflated into one predicate, is how a reader later cannot tell which rule refused a job." A1.2 The sensor mirrors Presence (presence.py:46-59) including its fail-closed default: assume_discharging_when_unknown. An unreadable battery is treated as discharging — a sensor that fails open re-creates the failure exactly when the system is least healthy. A1.3 AMENDS issue #12's own direction text. It says "health/preflight reads pmset"; preflight is the WRONG HOME — it runs in the caller's environment, not the daemon's, and open issue #19 is the standing proof (status reports sandbox present while every live run booted with it off). Refusal binds where dispatch happens. Preflight may display power state, never enforce it. A1.4 Tier 5 with a tier-4 test, stated as such — deliberately identical to what §2.7 claims for the memory ceiling. Power is a sampled reading of the physical world; tier 1 is unreachable and claiming it is the overclaim the enforcement ladder names as THE foot-gun. Honest limit recorded: dispatch-time refusal cannot stop an already-running job, so this would NOT have prevented Jul 24 outright — in-flight bounding is finding-0178's, not designed here. A1.5 The /tmp sampler is gone — cleared by the Aug 1 reboot. The tool built to diagnose the battery problem was destroyed by the battery problem, which is the argument for promotion in its sharpest form. Constrained by §1.2's anti-goal: ONE sensor feeding ONE predicate, not a family of ad-hoc probes. A1.6 carries the non-goals (no in-flight bounding, no thermal/CPU axis, no embedder residency — dn-local-model-runtime's boundary is untouched, no policy engine, no preflight enforcement), inferred ones marked [INFERENCE]. A1.7 carries four falsifiers, per §2.10 — the owner ratifies falsifiers, not proofs. Agent-drafted; lands only by the owner's merge. Frontmatter untouched.
…status proposed One plan, five items, 200k opus. Graduated from Amendment A1 in the preceding commit, so this plan's design_ref becomes ratified by the same merge that readies the plan — the two gates collapse into one, per the merge-gated regime. Grounded pass against HEAD 174d06c. The design turned out much smaller than issue #12's direction text implied, because the machinery already exists: Q1 A shed mechanism is already there — HEAVY_TIERS (supervisor.py:59), shed by blocked_tiers() on presence.foreground_active() (:130-135). This adds a predicate, not machinery. Q2 The ONE enforcement point is supervisor.py:177 — blocked_tiers() | model_blocked_tiers(), pinned at :153 ("Enforced at the ONE claim site … no new queue"). The power predicate joins that union; nothing else changes. Q3 The power rule may NOT live inside blocked_tiers() — the code says so in its own words (:131-134) and model_blocked_tiers() (:137) is the precedent for a sibling predicate. This is the plan's one hard constraint. Q4 presence.py:46-59 is the template: injectable probe, threshold, and the fail-closed default assume_present_when_unknown=True. Injectability is what makes the gate testable with no hardware. Q5 Preflight is not a valid home (scripts/palace.py, the caller's process) — issue #19 is the standing proof. Confirmed live: palace status reported "sandbox: podman present" from my shell, which says nothing about the daemon. Q6 Battery hardware healthy — Condition Normal, 95% capacity, 128 cycles (2026-08-05). The drain is load, not degradation. Q7 The /tmp sampler no longer exists to promote; the builder writes a sensor from the Presence template rather than porting a script. Item ordering IS the blast-radius order: sensor (pure) → predicate (computes a set nobody consumes) → compose (first behavior change) → the floor (the only write) → surface repair. Items 2 and 3 are deliberately separate so the predicate can land and be reviewed as inert, and Item 3's whole content is "and now it is actually consumed" — the finding-0187 failure this must not repeat (deleting bp-105's sweep call left 85/85 green). Item 3's acceptance is therefore that DELETING the new term from the union must redden a test. Two out-of-scope files are tripwires rather than omissions: presence.py (the template is read, never edited — the foreground gate keeps its meaning) and tests/integrity/test_shadow_isolation.py (asserts shadow_job.tier in HEAVY_TIERS; a red there means the tier set was reshaped instead of a predicate added — the wrong approach, and a stop-and-raise rather than a test edit). Acceptance-reachability checked: every §7 criterion's files appear in §5; mypy is directory-scoped so scheduler/power.py needs no enrollment; subprocess is already established in scheduler/ by presence.py, so no import-firewall question. §8 marked N/A — a threshold comparison and a set union are not mathematical objects, and inventing a field-guide entry would be the formalism §8 exists to refuse. Parallelizable with bp-151..153 (disjoint scope, different track). Noted in §12: this plan PROTECTS bp-153 — that rebuild is a long sliced job routing around a wedge the Jul 24 battery drain caused.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⚑ Merging this blesses Amendment A1 and readies bp-154 in one act
closes #12Two commits: the power-axis amendment to
dn-supervision-and-liveness(ratified,track: ops),and the one build plan graduated from it. The plan's
design_refbecomes ratified by the samemerge that readies the plan — the two gates collapse into one, per the regime. No status field is
edited.
Kept separate from #29 deliberately: that PR is
track: code-ingestand its merge means "Iratify
dn-vector-membership-store." Mixing an ops amendment in would make one merge mean twounrelated things, in a repo where the merge log is the build log.
Why — three emergencies, not the two the issue title names
code_backfilltimed out →code_syncwedged → the daemon died unwitnessed and stayed dead three days.Python:100%throughout. Caught at the wire; zero casualties under launchd KeepAlive.The battery hardware is healthy and stays healthy — re-measured 2026-08-05: Condition Normal,
95% maximum capacity, 128 cycles (up 4 from the frozen finding's 124). The drain is load, not
degradation. The scheduler is the defect.
§2.7 gave the supervisor exactly one resource axis to refuse on — memory (NN-8, "the scheduler
refuses breaching work"). That principle was never memory-specific; it simply never had a second
axis. A1 grows the same dispatch-time refusal a power axis: remaining energy is a schedulable
resource, and work that would spend the last of it is breaching work.
The design got much smaller once grounded
Issue #12's direction text implied new sensing infrastructure. The code says otherwise — the shed
machinery already exists, so this adds a predicate, not machinery:
HEAVY_TIERS = frozenset({"synthesis", "stretch"})(supervisor.py:59) is already shed byblocked_tiers()onpresence.foreground_active()(:130-135).blocked_tiers() | model_blocked_tiers()(
:177), pinned at:153: "Enforced at the ONE claim site … no new queue."scheduler/presence.py:46-59is an exact template — injectable probe, threshold, and thefail-closed default
assume_present_when_unknown: bool = True.⚑ The one hard pin, and it comes from the code's own words
The power rule gets its own predicate —
power_blocked_tiers()— never folded intoblocked_tiers().supervisor.py:131-134already ruled this shape:model_blocked_tiers()(:137) is the precedent for a sibling. Power is the third. Threepredicates, three questions, separately readable.
⚑ A1.3 amends the issue's own direction text
Issue #12 says "health/preflight reads
pmset -g batt". Preflight is the wrong home. It runsin the caller's environment, not the daemon's — and open #19 is the standing proof (
statusreports
sandbox: presentwhile every live run booted with it off). Confirmed live this session:palace statusreportedsandbox: podman presentfrom my shell, which says nothing about what thedaemon is under. Refusal must bind where dispatch happens. Preflight may display power state; it
may never enforce it.
Enforcement tier, stated honestly
Tier 5 with a tier-4 test — deliberately identical to what §2.7 claims for the memory ceiling
itself. Power is a sampled reading of the physical world; no value can be made to not inhabit "the
battery is low", so tier 1 is unreachable, and claiming it would be exactly the overclaim §0's
ladder names as the foot-gun.
The honest limit, recorded rather than hidden (A1.4): a dispatch-time refusal cannot stop a
job already running. Jul 24's
code_backfillwas already in flight when the throttle hit — sothis design would not have prevented that one outright. In-flight energy bounding needs the
job-timeout machinery (finding-0178) and is explicitly not designed here.
What the builder must not do (tripwires, not omissions)
scheduler/presence.pyis out of write scope — the template is read, never edited. Theforeground gate keeps its meaning.
tests/integrity/test_shadow_isolation.pyis out of write scope — it assertsshadow_job.tier in HEAVY_TIERS(:96-107). A red there means the tier set was reshapedinstead of a predicate added: the wrong approach, and a stop-and-raise rather than a test edit.
lands inert and Item 3's whole content is "and now it is actually consumed". Item 3's acceptance
is that deleting the new term from
:177must redden a test — the finding-0187 shape thismust not repeat (deleting bp-105's sweep call left 85/85 green).
assume_discharging_when_unknown— an unreadable battery is treated asdischarging.
Noneis a reachable, ordinary state here (absent tool, timeout,OSError), not atheoretical one, which is what makes this the plan's most important test.
How it was verified
Docs-only diff — zero Python touched. Per the pr skill this is a read-through plus structural
validation, not a code gate; I did not run pytest (and per #22 the local gate is unrunnable on a
main checkout). CI runs the verdicts.
What I did run: every
path:linere-opened at HEAD174d06c; the acceptance-reachability check(every §7 criterion's files appear in §5); structural validation (sections 0–12 complete, five
items,
write_scopeentries bare globs); and the enrollment checks — mypy is directory-scoped soscheduler/power.pyneeds no entry, andsubprocessis already established inscheduler/bypresence.py:14-33, so there is no import-firewall question.§8is marked N/A with a reason: a threshold comparison and a set union are not mathematicalobjects, and inventing a field-guide entry would be the formalism §8 exists to refuse.
Sequencing note
parallelizable_with: [bp-151, bp-152, bp-153]— disjoint write scopes (scheduler/**vscore/**+ops/**), different track. But the coupling runs one way and is worth stating:this plan protects bp-153. That rebuild is a long, sliced, resumable job whose D7 slicing exists
because the lane wedged once already — and the Jul 24 wedge was caused by a battery drain.
Landing bp-154 before bp-153 Item 3 runs is cheap insurance.
🤖 Generated with Claude Code
https://claude.ai/code/session_011LZZQPyGsoeGL73cbbEp3U