SAFETY-25: record the owner's decision - Option A, with ALLOC-01 as the revisit trigger #39
@@ -2,6 +2,64 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
**SAFETY-25's open question is ANSWERED: the BMS cut-out is the DELIBERATE and
|
||||
only backstop for demand-driven discharge, and a loop-side reserve floor is
|
||||
DEFERRED to `ALLOC-01` rather than rejected (SAFETY-25).** Decided by the
|
||||
product owner on 2026-09-02. **Option A is the shipped behaviour, so `app/`
|
||||
changed only where a comment claimed the question was still open** - no
|
||||
setpoint, no claim, no option, no default moved, and the 17-suite run is green
|
||||
on the same numbers as before.
|
||||
|
||||
* **Why deferring is the safe direction, and it is a property of the shape
|
||||
rather than a preference.** Option B's proof - `bms_soc_floor <
|
||||
loop_soc_floor <= peak_soc_floor`, verified through the real arbiter in
|
||||
`test_peakguard.py` part 10d - has an unstated premise: the reserve predicate
|
||||
and `discharge_permitted()` read the **same** state of charge. With two packs
|
||||
they read different numbers. The reserve binds on the emptier pack and files
|
||||
`hi = 0` while the peak guard, reading the other pack or an average, files
|
||||
`lo = excess > 0`; `lo > hi` is arbiter clause 3 and the answer is **0 W, the
|
||||
battery parked in the billed quarter** - the failure the memo itself marks
|
||||
fatal. **No config-ordering validator can catch it**, because it is a runtime
|
||||
divergence between two readings and not a config ordering, so CFG-01's shape
|
||||
of check would pass on the exact tree that parks the battery. Option A's cost
|
||||
is energy; Option B's cost built too early is a parked battery in the one
|
||||
quarter of the month that sets the bill.
|
||||
* **The revisit trigger is recorded explicitly, and it is `ALLOC-01`.** Under
|
||||
`ARCH-01`'s host-side allocator the two claims stop sharing a list: the loop
|
||||
floor becomes a **per-inverter `hi` consumed as an interval at stage A**,
|
||||
while the peak floor stays a **site `lo` at stage B**. Different claim lists,
|
||||
different stages, **structurally unable to cross** - which is a stronger
|
||||
argument than the ordering rule, because it makes the crossing unreachable
|
||||
rather than harmless. That is the shape in which Option B becomes safe to
|
||||
build, and the memo says so at the point a future reader will look.
|
||||
* **What will have to be RE-EARNED at N=2 is stated plainly rather than left
|
||||
for someone to discover.** `SAFETY-27` now names **five floors across two
|
||||
packs**, so "the SoC" stops being a scalar the config can bound. And
|
||||
`test_peakguard.py` **part 10f - the pin that made SAFETY-25 mergeable** -
|
||||
**cannot express two packs**: its dynamic half is a scalar 0-100 % SoC sweep
|
||||
against a single-series capture, and a two-pack exposure lives in the
|
||||
*difference* between two readings, which that sweep visits at no resolution.
|
||||
Its static half (the AST scan pinning the eight claim sources) still bites at
|
||||
N=2; the dynamic half goes silent. Merging a two-pack reserve on 10f's
|
||||
current green would be merging on a check that cannot see the failure.
|
||||
* **The options analysis is kept intact on purpose.** A memo that recorded the
|
||||
verdict and deleted the reasoning is the failure mode this `DECISIONS/`
|
||||
directory exists to prevent, so §1-§7 stand exactly as written - including
|
||||
§5's *"(recommended)"* on Option B and §6's recommendation - with the new §0
|
||||
saying what was decided and §6 marked as answered. Part 10e's check moved
|
||||
with the memo: it used to pin the honest `Status: OPEN` line and now pins
|
||||
that the memo names the option, the date and the trigger **and still carries
|
||||
both options and the recommendation**.
|
||||
* **The tripwire stays, and "deferred" is why it stays.** Part 10 and mutation
|
||||
13 are unchanged in substance: a ninth claim source in `Controller.cycle()`
|
||||
still takes 10f red on the line it is written. Deferred, not rejected, is
|
||||
precisely the state in which someone builds Option B by accident.
|
||||
|
||||
Operator-facing surfaces follow the decision and nothing else changed on them:
|
||||
`DOCS.md`'s SAFETY-25 section now says a choice was made and points at the
|
||||
trigger, and `app/freeze.py`'s finding and `app/peakguard.py`'s point 2 say
|
||||
"decided, on purpose" where they said "open".
|
||||
|
||||
**`PeakStore.record_block()` now has its POLICY ARGUMENT graded, and the
|
||||
SAFETY-20 import check now looks at all three ways of writing an import
|
||||
(TEST-13).** Two survivors from TARIFF-01's review, both closed in
|
||||
|
||||
@@ -1,20 +1,156 @@
|
||||
# SAFETY-25 — does the grid-following loop get a reserve floor?
|
||||
|
||||
**Status: OPEN. This is a memo, not a decision.** It sets out the options, what
|
||||
each costs, what each protects, and a recommendation. **The choice belongs to the
|
||||
product owner**, because it decides whether this house's battery is allowed to
|
||||
discharge to its BMS cut-out on a sustained-load evening, and that moves real
|
||||
energy.
|
||||
**Status: DECIDED 2026-09-02 — Option A.** The product owner has answered §1:
|
||||
**the BMS cut-out stays the only backstop for demand-driven discharge on the
|
||||
grid-following loop.** **Option B — a loop-side reserve floor — is DEFERRED**,
|
||||
not rejected, with an explicit revisit trigger recorded in §0c: **`ALLOC-01`**.
|
||||
|
||||
Filed with SAFETY-25. The other half of that ticket — making `peak_soc_floor`'s
|
||||
scope honest in the config, the docs and on the status page — **shipped in the
|
||||
same PR** and did not wait on this, because it is required whichever way the
|
||||
answer goes.
|
||||
Option A **is the shipped behaviour**, so recording this decision changed no
|
||||
code. The honesty half of SAFETY-25 — `peak_soc_floor`'s scope made true in the
|
||||
config, in `DOCS.md` and on the status page — had already shipped and was
|
||||
required under every option. What was missing was the answer, and the answer is
|
||||
now here rather than in a Trello card that scrolls away.
|
||||
|
||||
**Everything from §1 down is the memo exactly as it was written before the
|
||||
decision, and is deliberately left intact.** It is the record of *why*, and a
|
||||
future reader arriving at the revisit trigger needs the options analysis, the
|
||||
costs, the measured replay and the trap in Option B's obvious shape — not a
|
||||
one-line verdict. §5 still labels Option B *"(recommended)"* and §6 still says
|
||||
"Option B is what I would build"; those were the engineering recommendation put
|
||||
to the owner, they are preserved as written, and **§0 is what was decided.**
|
||||
|
||||
Filed with SAFETY-25. Card 6a94b2b98b73123d520e69f2. The other half of that
|
||||
ticket — making `peak_soc_floor`'s scope honest in the config, the docs and on
|
||||
the status page — **shipped in SAFETY-25's own PR** and did not wait on this,
|
||||
because it was required whichever way the answer went.
|
||||
|
||||
*This directory exists so a recorded decision has somewhere to live that is not a
|
||||
comment in a source file nobody reads and not a Trello card that scrolls away.
|
||||
`SAFETY-27` asks for a second one (the naming of the floors); it belongs here
|
||||
too.*
|
||||
too, and §0d says what N=2 does to it.*
|
||||
|
||||
---
|
||||
|
||||
## 0. The decision
|
||||
|
||||
### 0a. What was decided, and the owner's reasoning
|
||||
|
||||
**Option A. The BMS remains the only backstop for demand-driven discharge on the
|
||||
grid-following loop. Option B is deferred until `ALLOC-01` exists.**
|
||||
|
||||
The reasoning is not "Option B is wrong" — §5 and §6 still stand as engineering.
|
||||
It is that **deferring is the safe direction, and building now is not.**
|
||||
|
||||
Option B's safety proof is the ordering constraint in §5:
|
||||
|
||||
bms_soc_floor < loop_soc_floor <= peak_soc_floor
|
||||
|
||||
That proof has an unstated premise: **the reserve predicate and
|
||||
`discharge_permitted()` must read the SAME state of charge.** At N=1 they do,
|
||||
which is why the proof reads as airtight, and why part 10d can run it through
|
||||
the real arbiter and watch the contradiction disappear.
|
||||
|
||||
**With two packs they read different numbers.** The reserve binds on whichever
|
||||
pack is emptier and files `hi = 0`; the peak guard, reading the other pack or an
|
||||
average of the two, is *not* floor-limited and files `lo = excess > 0`. Then
|
||||
`lo > hi`, arbiter clause 3 fires, and the result is **0 W — the battery parked,
|
||||
in the billed quarter.** That is precisely the failure §5 marks *"⚠️ fatal if
|
||||
missed"*, arriving by a route the ordering rule cannot close, because
|
||||
`loop_soc_floor <= peak_soc_floor` is still perfectly satisfied while it happens.
|
||||
|
||||
**And no config-ordering validator can catch it.** The rule §5 proposes — refuse
|
||||
the bad ordering at startup, in CFG-01's manner — grades *configuration*. This is
|
||||
a **runtime divergence between two readings**, not a config ordering. The startup
|
||||
check would pass, every time, on the exact tree that parks the battery.
|
||||
|
||||
So the shape of the risk decides the direction: **Option A's cost is energy
|
||||
(§5, "every watt below the reserve becomes billed import" — inverted: the pack
|
||||
may sit on its own cut-out). Option B's cost, built too early, is a parked
|
||||
battery in the one quarter of the month that sets the bill.** One of those is a
|
||||
tariff line; the other is the failure this memo already calls fatal. Take the
|
||||
one that cannot park the battery, and build Option B when its proof holds again.
|
||||
|
||||
### 0b. What this decision does NOT say
|
||||
|
||||
- It does **not** say a loop-side reserve is a bad idea. §5's protection
|
||||
argument — keeping the pack off its own cut-out, which is the `MAINT-01` drift
|
||||
condition — is unchanged and still wanted.
|
||||
- It does **not** close §4's fifth-key question. `SAFETY-27` still owns the
|
||||
naming of the floors, and any future `loop_soc_floor` still lands after it.
|
||||
- It does **not** weaken §6 item 3: **the tripwire in `test_peakguard.py`
|
||||
part 10 stays.** A deferred option needs its tripwire more than an open one
|
||||
does, because "deferred" is exactly the state in which someone builds it by
|
||||
accident. Part 10f still takes red on the line a ninth claim source is
|
||||
written. Do not remove it.
|
||||
|
||||
### 0c. The revisit trigger: `ALLOC-01`
|
||||
|
||||
**`ALLOC-01` is the trigger, and it is a trigger because it changes the SHAPE of
|
||||
the two claims, not because it changes anyone's mind.**
|
||||
|
||||
`ARCH-01` decided that the site grid target is **allocated host-side, above the
|
||||
driver seam**: one integrator on P1, a sum-invariant split into per-inverter
|
||||
shares, and each inverter told a function of **its own share** only. It also
|
||||
decided that **per-inverter constraints are consumed by the allocator as
|
||||
INTERVALS, never filed as site-level claims** — which is the structural fix for
|
||||
the cross-inverter `lo > hi -> 0 W` contradiction.
|
||||
|
||||
Under the allocator, the two claims that collide in §0a stop sharing a claim
|
||||
list at all:
|
||||
|
||||
| | today (N=1, no allocator) | under `ALLOC-01` |
|
||||
|---|---|---|
|
||||
| the loop reserve | a site `hi` at `P_LOOP` | a **per-inverter `hi`**, consumed at **stage A** as that unit's interval |
|
||||
| the peak floor | a site `lo` at `P_PEAK` | still a **site `lo`**, filed at **stage B** against the site target |
|
||||
|
||||
**Different claim lists, evaluated at different stages, structurally unable to
|
||||
cross.** The reserve bounds one inverter's share; the peak floor bounds the site
|
||||
sum. There is no arbiter resolution in which one is the other's `lo` and `hi`,
|
||||
so §0a's failure cannot be constructed — not "is guarded against", *cannot be
|
||||
constructed*. That is the same class of argument as §5's ordering rule but a
|
||||
stronger one: §5 makes the crossing harmless, `ALLOC-01` makes it unreachable.
|
||||
|
||||
**That is the shape in which Option B becomes safe to build.** When `ALLOC-01`
|
||||
exists, reopen this memo at §5 Option B, restate the reserve as a stage-A
|
||||
per-inverter interval, and the ordering rule becomes an implementation
|
||||
convenience rather than the load-bearing proof.
|
||||
|
||||
### 0d. What has to be RE-EARNED at N=2, stated plainly
|
||||
|
||||
Two things this decision leans on do not survive a second pack, and saying so is
|
||||
part of the decision rather than a footnote to it.
|
||||
|
||||
**1. `SAFETY-27` now names FIVE floors across TWO packs.** §4's table already
|
||||
carries four keys plus "the loop"; two of them ship the same default of 20 from
|
||||
different cards. At N=2 each of those is a floor *per pack*, and "the SoC" stops
|
||||
being a scalar the config can bound — which is the same divergence that makes
|
||||
§0a's contradiction possible. `SAFETY-27` inherits a harder question than the
|
||||
one it was filed with.
|
||||
|
||||
**2. `test_peakguard.py` part 10f — the guarantee that made SAFETY-25 mergeable —
|
||||
CANNOT EXPRESS TWO PACKS, and will not cover the two-pack case.** Say it flatly,
|
||||
because it is the load-bearing pin (§6 item 3: PR #31 shipped this section
|
||||
claiming a tripwire it did not have, and the reviewer disproved the claim by
|
||||
building Option B under a green suite):
|
||||
|
||||
- 10f's dynamic half is a **scalar 0–100 % SoC sweep** — one number swept over
|
||||
one range, in the idle and floor-limited states — against a **single-series
|
||||
capture** (§7: the fixtures carry signed net power at the meter and nothing
|
||||
else; the pack is modelled, and one pack is modelled).
|
||||
- A two-pack exposure lives in the **difference** between two SoC readings. A
|
||||
sweep over one scalar visits no point of that space, at any resolution. It
|
||||
cannot fail on the divergence because it cannot represent it.
|
||||
- 10f's **static** half — the AST scan pinning the eight claim sources in
|
||||
`Controller.cycle()` — survives, and is the half that keeps working: a ninth
|
||||
source still goes red on the line it is written, at N=2 as at N=1. It is the
|
||||
*dynamic* half, the one that proves the named sources actually reach the
|
||||
arbiter across the whole state space, that goes silent.
|
||||
|
||||
So: **10f remains a valid tripwire against building Option B today, and is not a
|
||||
guarantee about N=2.** Whoever reaches the `ALLOC-01` revisit inherits the job of
|
||||
re-earning it — a two-pack fixture with two SoC series, or a claim-shape argument
|
||||
at stage A that does not need one. Merging a two-pack reserve on 10f's current
|
||||
green is merging on a check that cannot see the failure.
|
||||
|
||||
---
|
||||
|
||||
@@ -216,6 +352,11 @@ around.
|
||||
|
||||
## 6. Recommendation
|
||||
|
||||
⚠️ **This section is the recommendation that was PUT to the owner, kept as
|
||||
written. It was answered on 2026-09-02 — see §0. The owner took Option A and
|
||||
deferred Option B to `ALLOC-01`; item 3 below (keep the part 10 tripwire)
|
||||
was taken as written and still stands.**
|
||||
|
||||
**Option A is honest and cheap; Option B is what I would build.** Concretely:
|
||||
|
||||
1. **Now (shipped with SAFETY-25):** the honesty half. `peak_soc_floor`, its
|
||||
|
||||
@@ -619,10 +619,16 @@ rail. What it means is that the *depth* your pack reaches on a heavy evening is
|
||||
set by your house, not by any number in this file, and the BMS is the only thing
|
||||
underneath it.
|
||||
|
||||
**Whether it should stay that way is an open product decision**, recorded with its
|
||||
options, costs and a recommendation in
|
||||
[`DECISIONS/SAFETY-25-loop-soc-floor.md`](DECISIONS/SAFETY-25-loop-soc-floor.md).
|
||||
Until it is answered, this section is the honest statement of what you have.
|
||||
**This is now a recorded decision, not an open question.** On **2026-09-02** the
|
||||
product owner took **Option A**: the BMS cut-out is **deliberately** the only
|
||||
backstop for demand-driven discharge, and a loop-side reserve floor is
|
||||
**deferred** rather than rejected. The options, their costs, the measured replay
|
||||
and the reasoning are in
|
||||
[`DECISIONS/SAFETY-25-loop-soc-floor.md`](DECISIONS/SAFETY-25-loop-soc-floor.md),
|
||||
together with the revisit trigger (`ALLOC-01`, the host-side share allocator)
|
||||
under which a reserve becomes safe to build. **Nothing about the behaviour above
|
||||
changed** — Option A is what has always shipped. What changed is that this
|
||||
section now describes a choice somebody made, and you can read why.
|
||||
|
||||
### The winding-loop alarm (the output clamp)
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ THE FAILURE NEEDS NO FAULT ANYWHERE ELSE. A frozen-but-talking meter is
|
||||
"there is one SoC floor and it is maintenance's" - but neither of the new
|
||||
ones binds THIS loop, and `peak_soc_floor`'s name reads as though it does.
|
||||
SAFETY-25 replayed the exposure (test_peakguard.py part 10) and recorded
|
||||
the options in DECISIONS/SAFETY-25-loop-soc-floor.md; whether the loop gets
|
||||
a reserve of its own is an open product decision, not an oversight.
|
||||
the options in DECISIONS/SAFETY-25-loop-soc-floor.md. DECIDED 2026-09-02,
|
||||
Option A: the BMS is the sole energy backstop for demand-driven discharge
|
||||
ON PURPOSE. A loop-side reserve is DEFERRED, not rejected - its revisit
|
||||
trigger is ALLOC-01. So the sentence above is a recorded decision, not an
|
||||
oversight and no longer an open question.
|
||||
|
||||
The loop reads a constant error and integrates it. Time from freeze to the rail,
|
||||
replaying the real `control.compute()` at 15 s cycles:
|
||||
|
||||
@@ -300,7 +300,10 @@ the one place the question is answered. Four things about the shape of it:
|
||||
PEAK GUARD FLOOR-LIMITED while the arbiter grants the loop up to 2000 W of
|
||||
discharge in every one of them. test_peakguard.py part 10 is the replay;
|
||||
DECISIONS/SAFETY-25-loop-soc-floor.md carries the options and the
|
||||
recommendation, and the decision itself is the product owner's and is open.
|
||||
recommendation, and the product owner DECIDED it on 2026-09-02: Option A,
|
||||
the BMS stays the only backstop for demand-driven discharge, with a
|
||||
loop-side reserve DEFERRED to ALLOC-01. This floor's scope is therefore
|
||||
settled rather than provisional - it bounds the peak guard, on purpose.
|
||||
|
||||
3. IT IS GRADED ONLY WHERE IT IS NOT DEGENERATE. The SoC question is asked
|
||||
AFTER `wanted > 0` is established - i.e. only where a forced discharge is
|
||||
|
||||
@@ -1877,7 +1877,8 @@ print("10. SAFETY-25: the loop discharges past this floor, and how far past")
|
||||
# point. The exposure must not be able to change under a green suite; it has
|
||||
# to be closed deliberately, and closing it means answering AC 2 and
|
||||
# deleting or inverting these checks along with
|
||||
# DECISIONS/SAFETY-25-loop-soc-floor.md's open status.
|
||||
# DECISIONS/SAFETY-25-loop-soc-floor.md's status - OPEN when this was
|
||||
# written, DECIDED (Option A, deferred B) since 2026-09-02.
|
||||
#
|
||||
# ⚠️ AND IT IS 10f THAT MAKES THAT SENTENCE TRUE, NOT 10a-10c. The replay
|
||||
# below hand-builds main.py's claim list, so on its own it is blind to a
|
||||
@@ -2131,8 +2132,11 @@ check("THE ASYMMETRY, ON REAL DATA. On every cycle where the guard reports "
|
||||
and HOT_R["max_granted"] > 0.0)
|
||||
check("...which is why SAFETY-24's floor could not be widened into this one, "
|
||||
"and why the fix is not a bigger number. Recorded in "
|
||||
"DECISIONS/SAFETY-25-loop-soc-floor.md; AC 2 is the product owner's and "
|
||||
"is open", HOT_R["min_soc"] <= BMS_FLOOR + 0.05)
|
||||
"DECISIONS/SAFETY-25-loop-soc-floor.md, where AC 2 was DECIDED on "
|
||||
"2026-09-02: Option A, the BMS stays the only backstop, and the "
|
||||
"loop-side reserve is DEFERRED to ALLOC-01 - so this replay is not a "
|
||||
"pending exposure but the RECORDED behaviour, and part 10 stays as its "
|
||||
"tripwire", HOT_R["min_soc"] <= BMS_FLOOR + 0.05)
|
||||
|
||||
# --- 10d. the trap in the obvious fix, checked rather than asserted ---------
|
||||
# ⚠️ THE MEMO'S CENTRAL ENGINEERING CLAIM, RUN THROUGH THE REAL ARBITER. The
|
||||
@@ -2182,12 +2186,30 @@ check("...and DOCS.md says it in the options table AND in the section, with the
|
||||
"not a pack-wide reserve" in _docs_src
|
||||
and "IT BOUNDS THE PEAK GUARD ONLY" in _docs_src
|
||||
and "SAFETY-25" in _docs_src)
|
||||
check("...and the decision memo AC 1 asks for is COMMITTED, carries the options "
|
||||
"and a recommendation, and is honest that the decision is not taken",
|
||||
# ⚠️ THE DECISION LANDED (2026-09-02, Option A) AND THIS CHECK MOVED WITH IT.
|
||||
# It used to pin "Status: OPEN. This is a memo, not a decision." - the memo being
|
||||
# HONEST that AC 2 was unanswered. AC 2 is answered now, so the same check pins
|
||||
# the same property in its new form: the memo states WHICH option, WHEN, and its
|
||||
# REVISIT TRIGGER, and it still carries the options and the recommendation
|
||||
# UNCHANGED. A memo that recorded the verdict and deleted the reasoning would be
|
||||
# the failure this is guarding against, which is why "Recommendation" and both
|
||||
# rejected options are still asserted here rather than only the status line.
|
||||
_memo_txt = _memo.read_text(encoding="utf-8")
|
||||
check("...and the decision memo AC 1 asks for is COMMITTED, records AC 2 as "
|
||||
"DECIDED with a date, an option and a revisit trigger, and STILL carries "
|
||||
"the options analysis and the recommendation that were put to the owner",
|
||||
_memo.exists()
|
||||
and "Status: OPEN. This is a memo, not a decision." in _memo.read_text(
|
||||
encoding="utf-8")
|
||||
and "Recommendation" in _memo.read_text(encoding="utf-8"))
|
||||
and "Status: DECIDED 2026-09-02 — Option A." in _memo_txt
|
||||
and "ALLOC-01" in _memo_txt
|
||||
and "Recommendation" in _memo_txt
|
||||
and "### Option A" in _memo_txt and "### Option B" in _memo_txt)
|
||||
check("...and it says plainly that part 10f - the pin that made SAFETY-25 "
|
||||
"mergeable - is a SCALAR SoC sweep against a single-series capture and "
|
||||
"so cannot express two packs. The tripwire's own limits belong in the "
|
||||
"memo that leans on it, or the next reader merges a two-pack reserve on "
|
||||
"a green check that cannot see the failure",
|
||||
"10f" in _memo_txt and "SAFETY-27" in _memo_txt
|
||||
and "cannot express two packs" in _memo_txt.lower())
|
||||
check("...and DOCS.md sends the reader to it rather than leaving the open "
|
||||
"question implicit",
|
||||
"DECISIONS/SAFETY-25-loop-soc-floor.md" in _docs_src)
|
||||
@@ -2288,8 +2310,12 @@ check("...and so does the persistent notification, which outlives the page and "
|
||||
# The whole inventory of claim sources main.py can file, in any configuration.
|
||||
# ⚠️ ADDING TO THIS SET IS A DELIBERATE ACT. If you are here because you added a
|
||||
# claim to `Controller.cycle()` and this went red, that is the tripwire working:
|
||||
# read DECISIONS/SAFETY-25-loop-soc-floor.md, answer AC 2 in it, and update
|
||||
# part 10 and mutation 13 rather than this line alone.
|
||||
# read DECISIONS/SAFETY-25-loop-soc-floor.md and update part 10 and mutation 13
|
||||
# rather than this line alone. ⚠️ AC 2 IS DECIDED (2026-09-02, Option A) AND
|
||||
# THAT DOES NOT RETIRE THIS SET - Option B is DEFERRED, not rejected, and
|
||||
# "deferred" is exactly the state in which a ninth source gets built by
|
||||
# accident. Its revisit trigger is ALLOC-01, under which the reserve becomes a
|
||||
# PER-INVERTER interval at the allocator rather than a ninth claim here.
|
||||
CLAIM_SOURCES = {"device", "supervised", "failsafe", "safety", "peak",
|
||||
"arbitrage", "maintenance", "loop"}
|
||||
# The two appends that file a claim BUILT ELSEWHERE in the method. Named here so
|
||||
|
||||
Reference in New Issue
Block a user