Skip to content

Commit 79397a8

Browse files
pt9912claude
andcommitted
docs(adr): ADR 0070 Scenario-Scheduled Device Commands (Provisional, S0) — Trigger 046
S0 der Scenario-Scheduled-Commands-Welle (Owner-Mandat "Voller Mechanismus"): - ADR 0070 (Proposed -> Provisional): Top-Level commands-Block analog faults (ADR 0022/0059), Punkt-in-der-Zeit; ScenarioCommand + Scenario.commands (default leer, pin-neutral); ScenarioCommandEngine + TickLoop-Vor-Tick-Naht (scenario- vor Agent-Commands); scenario_hash deckt commands ab (Variante A, Decimal-Strings); nicht-idle E2E je SOLLTE-Geraet (Wind = IGNORED). Nested-per-Device + Agents-only-Deckung als Alternativen verworfen (ADR §3). - Slice-Plan in-progress/scenario-scheduled-device-commands.md (S0..S3). - adr/README: 0070-Eintrag + 0069-Status-Fix (Provisional -> Accepted; war beim Multi-Run-Closure in der Index-Zeile uebersehen worden). - carveouts T-046 -> In-Arbeit; open/046-Status -> aktiviert. Kein Code (S0 = Design; Provisional braucht keine Code-Validierung, ADR 0006). Validierung: make docs-check gruen (252 Dateien, 0 Befunde). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 09beb21 commit 79397a8

5 files changed

Lines changed: 244 additions & 3 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# ADR 0070 — Scenario-Scheduled Device Commands: `commands`-Block + ScenarioCommandEngine (Provisional)
2+
3+
**Status:** Provisional — Owner traegt die Empfehlung mit (S0, full-Mechanismus-
4+
Mandat; [`ADR 0006`](0006-adr-lifecycle-superseding-and-process-corrections.md) §2); die
5+
Validierung laeuft ueber S1..S3 des
6+
[`in-progress`-Slice-Plans](../planning/in-progress/scenario-scheduled-device-commands.md).
7+
`Accepted` folgt bei der Implementierungs-Wellen-Closure (gates gruen); bis dahin
8+
bleibt der Entwurf schaerfbar — offener Platzierungs-Punkt (§2.1 top-level vs.
9+
nested) ist begruendet entschieden, aber noch **kein** immutable Beschluss.
10+
**Datum:** 2026-06-18
11+
**Status geaendert am:** 2026-06-18 — `Proposed → Provisional` (S0; Owner-
12+
Mittragung des Voll-Mechanismus statt der schlanken Agents-Deckung).
13+
**Bezug:**
14+
15+
- [`ADR 0022`](0022-fault-injection-protocol.md) + [`ADR 0059`](0059-generic-scenario-fault-engine.md)
16+
die tick-genaue **Fault-Planung** (`faults`-Block + `ScenarioFaultEngine`) ist
17+
das strukturelle Template; diese ADR zieht den analogen Command-Pfad ein
18+
(kein Supersedes, Pattern [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)).
19+
- [`ADR 0013`](0013-device-model-protocol.md) §2.3 — `apply_command`-Vertrag +
20+
Command-Reihenfolge aus der Scenario-Source; hier um eine **scenario-getriebene**
21+
Quelle erweitert (bisher nur Agents/Inline).
22+
- [`ADR 0027`](0027-rule-based-agent-scenario-pattern.md) — der bestehende
23+
Agents/Rules-Command-Pfad; bewusste Abgrenzung (§3).
24+
- [`ADR 0021`](0021-scenario-loader-and-tick-loop-event-wiring.md) — Loader- +
25+
TickLoop-Event-Verdrahtung (optionale Top-Level-Bloecke, pin-neutral default);
26+
das `commands`-Schema + die Engine-Naht setzen genau hier an.
27+
- [`ADR 0051`](0051-fault-engine-location-and-naming.md) — Placement-/Naming-
28+
Konvention der Scenario-Engines (Vorbild fuer `ScenarioCommandEngine`).
29+
- [Trigger 046](../planning/open/046-command-driven-integration-e2e.md)
30+
der aufloesende Trigger (Command-E2E der SOLLTE-Geraete).
31+
32+
---
33+
34+
## 1. Kontext
35+
36+
Jedes SOLLTE-Geraet ([`GG-DEV-015`](../../../spec/lastenheft.md#gg-dev-015)..018)
37+
implementiert `apply_command` ([`ADR 0013`](0013-device-model-protocol.md)), aber
38+
die vier Szenario-Smokes fahren **idle**: der `devices`-Layer kennt **keinen**
39+
scenario-scheduled-Command-Mechanismus. Faults werden tick-genau geplant
40+
(`faults`-Block → [`ScenarioFaultEngine`](0059-generic-scenario-fault-engine.md)
41+
`TickLoop` Schritt A2), Commands nicht. Commands erreichen `apply_command` heute
42+
nur ueber zwei Quellen:
43+
44+
- **Agents** (`rule_based`, [`ADR 0027`](0027-rule-based-agent-scenario-pattern.md)):
45+
`agents.<name>.rules[*].action``_pending_agent_commands``TickLoop`-Schritt
46+
A0a (`_apply_pending_agent_commands`) → `device.apply_command`.
47+
- **Inline Load-Overlay** (`load_events`/`load_profiles`) — direkte Calls, keine
48+
`Command`-Objekte.
49+
50+
Die generische Command-Routing-Mechanik ist damit gedeckt; es fehlt der
51+
**geraetespezifische, scenario-deklarierte** E2E-Pfad „Command kommt via Szenario
52+
rein → SOLLTE-Geraet reagiert sichtbar im Snapshot".
53+
54+
## 2. Decision
55+
56+
### 2.1 Schema — optionaler Top-Level-`commands`-Block
57+
58+
Ein optionaler Top-Level-`commands`-Block, **strukturell analog zu `faults`**
59+
(beide Top-Level, beide zielen auf ein `target`-Device):
60+
61+
```yaml
62+
commands:
63+
- simulation_time: 5000 # ms; zugestellt am Tick, dessen Span simulation_time enthaelt
64+
target: "ev-1" # device_id
65+
type: "set_charge_power"
66+
payload: { value: "20" } # Decimal-als-String (Variante A — canonical_json verbietet float)
67+
```
68+
69+
**Punkt-in-der-Zeit** (kein `duration_ms` wie bei Faults): ein Command wird genau
70+
**einmal** am geplanten Tick zugestellt (kein Fenster, kein Recovery).
71+
72+
**Top-level statt nested im `devices`-Block** (begruendete Wahl): spiegelt `faults`
73+
1:1 → einheitlicher Loader-/Validator-/`canonical_json`-/Hash-Pfad, und die
74+
Geraete-Definition bleibt rein deklarativ (keine eingebettete Zeitreihe). Die
75+
Trigger-046-Formulierung „im `devices`-Block" meint *an die Geraete gerichtete*
76+
Commands, nicht eine YAML-Verschachtelung. (Nested-per-Device als Alternative in
77+
§3 verworfen.)
78+
79+
### 2.2 Domain — `ScenarioCommand` + `Scenario.commands`
80+
81+
`ScenarioCommand` (frozen dataclass, analog `ScenarioFault`):
82+
`simulation_time: int`, `target: str`, `type: str`, `payload: Mapping[str, object]`.
83+
NEU optionales Feld `Scenario.commands: tuple[ScenarioCommand, ...] = ()` (Default
84+
leer, analog `agents`/`load_events` — **pin-neutral** fuer alle Bestands-Szenarien).
85+
Loader: `_build_commands`/`_build_command` analog `_build_faults`/`_build_fault`.
86+
Validator-Strang fuer den `commands`-Block analog dem `faults`-Strang.
87+
88+
### 2.3 ScenarioCommandEngine + TickLoop-Naht
89+
90+
`ScenarioCommandEngine` ([`ADR 0051`](0051-fault-engine-location-and-naming.md)-
91+
Naming) haelt die geplanten Commands und liefert pro Tick die **faelligen**
92+
`Command`-Objekte: `due_commands(context) -> Sequence[Command]` — jene, deren
93+
`simulation_time` in den aktuellen Tick-Span faellt. Jeder `ScenarioCommand` →
94+
ein [`Command`](../../../src/grid_gym/hexagon/core/domain/command.py)
95+
(`command_id="scenario-cmd-<i>"`, `target_device_id=target`, `type`, `payload`,
96+
`validation_status="scenario"`).
97+
98+
`TickLoop`-Naht: eine neue Vor-Tick-Stufe (analog Fault-Schritt A2) speist die
99+
faelligen Commands in den **bestehenden** Apply-Pfad
100+
(`_device_by_id[target].apply_command`, wie `_apply_pending_agent_commands`).
101+
**Determinismus-Reihenfolge** (festzulegen, [`ADR 0013`](0013-device-model-protocol.md) §2.3):
102+
scenario-Commands werden in Scenario-Source-Reihenfolge zugestellt und laufen
103+
**vor** den Agent-Commands desselben Ticks (scenario-Commands sind externe
104+
geplante Inputs wie Faults; Agents reagieren auf den dadurch entstandenen
105+
Zustand). Nicht-existentes `target` → typisierter Loader-/Validierungs-Fehler
106+
(kein Silent-Drop).
107+
108+
### 2.4 Canonical / Hash / Determinismus
109+
110+
`commands` ist Teil von `Scenario` → automatisch in
111+
`canonical_json(asdict(scenario))` → **`scenario_hash`**. Payload-Werte sind
112+
Decimal-als-String (Variante A; `canonical_json` verbietet `float`). Determinismus
113+
([`GG-SIM-001`](../../../spec/lastenheft.md#gg-sim-001)/004,
114+
[`GG-MVP-002`](../../../spec/lastenheft.md#gg-mvp-002)-Replay): gleicher
115+
`scenario_hash` + Seed → identischer Command-Zeitplan → byte-identische Telemetrie.
116+
**Pin-neutral**: Szenario ohne `commands` → leeres Tupel → Hash unveraendert →
117+
Bestands-Pins (inkl. die vier SOLLTE-Idle-Smokes) bit-genau gueltig.
118+
119+
### 2.5 E2E-Scope (S3, schliesst Trigger 046)
120+
121+
Je SOLLTE-Geraet ein **nicht-idle** Integration-E2E (Command via `commands`-Block
122+
geplant → Snapshot-Assertion):
123+
124+
- **EV-Charger**: `set_charge_power` → `power_kw` springt am geplanten Tick (+ SoC
125+
bewegt sich); `set_plug_state` optional.
126+
- **Transformer**: `set_power_kw` → `primary_power_kw`.
127+
- **Diesel**: `set_power_kw` (>=0) → `power_kw`/`running`.
128+
- **Wind-Turbine**: nimmt **keine** Commands ([`ADR 0057`](0057-wind-turbine-device-pattern.md)
129+
§2.1 → `apply_command` = `CommandResult.IGNORED`). E2E belegt: Command wird via
130+
Zeitplan zugestellt, Wind **ignoriert** ihn (`power_kw` bleibt wettergetrieben) —
131+
positiver Beleg der `IGNORED`-Semantik.
132+
133+
## 3. Verworfene Alternativen
134+
135+
- **Nested `commands:` pro Device-Definition** — bricht die `faults`-Analogie,
136+
verteilt Zeitplan-Logik in die Geraete-Bloecke, erschwert Validator/Canonical
137+
(heterogene Device-Schemata). Verworfen zugunsten Top-Level (§2.1).
138+
- **Nur Agents/Rules-Deckung** (schlanker Pfad) — deckt die `apply_command`-
139+
Integrations-Luecke, liefert aber **nicht** den scenario-deklarierten
140+
`devices`-Command-Mechanismus, den Trigger 046 als Closure-Kriterium fordert.
141+
Vom Owner zugunsten des Voll-Mechanismus verworfen.
142+
143+
## 4. Konsequenzen
144+
145+
- NEU: `commands`-Schema + `ScenarioCommand` + Loader + `ScenarioCommandEngine` +
146+
`TickLoop`-Naht + 4 SOLLTE-E2E (Slice-Plan S1..S3).
147+
- `scenario_hash` deckt jetzt `commands` ab (additive, default-leere Erweiterung).
148+
- Out-of-Scope: bedingte/konditionale Commands (nur tick-geplant), Command-Result-
149+
Rueckkopplung ins Szenario, Runtime-API-Command-Injektion (Agents/API-Pfad),
150+
Recovery/Fenster-Semantik (Punkt-in-der-Zeit).

docs/plan/adr/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ historisch.
8787
| 0066 | [Battery-Zellspannung-Telemetrie (M8 Welle 4b)](0066-battery-cell-voltage-telemetry-pattern.md) | Accepted | 2026-06-17 | M8-Welle-4b (Design C1 + Umsetzung C2; **schliesst [`GG-BESS-007`](../../../spec/lastenheft.md#gg-bess-007)**). Additive Schaerfung von [`ADR 0014`](0014-battery-snapshot-schema.md) ohne Supersedes (Pattern [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)); Schwester-Slice zu [`ADR 0065`](0065-battery-thermal-telemetry-pattern.md) (4a) + **erster Battery-`RandomPort`-Konsum** (Praezedenz [`ADR 0057`](0057-wind-turbine-device-pattern.md)). NEU `CellConfig` (nested, opt-in) in `BatteryConfig` (`nominal_pack_voltage_v>0`, `n_cells>=1`, `noise_amplitude_v>=0`). `cell_voltages_v` derived je Tick: Basis `nominal_pack_voltage_v/n_cells`; bei `noise_amplitude_v>0` pro Zelle `(draw*2-1)*amp` aus `random.sub_port("cell-<i>").sub_port("tick-<t>").next_float()` — per-Zelle unabhaengig, per-Tick variierend, **tick-gekeyt → resume-kontinuierlich**; `noise=0` → alle Zellen identisch (kein RandomPort-Zug). Opt-in aggregierte `cell_voltage_delta_v`-`TelemetryPoint` (`max-min`, `unit="V"`) **nur bei aktivem Block** (bounded statt N per-Zelle); Emission alphabetisch sortiert (`cell_voltage_delta_v` vor `power_kw`). `BatterySnapshot` opt-in serialisiert (Config-`cell`-Block mit `n_cells:int` + Top-Level `cell_voltages_v: tuple[Decimal,...]` nur bei Non-Empty) **ohne Versions-Bump** (v1-Lesepfad, Tuple-Kanonik). **Resume-Vertrag** ([`ADR 0057`](0057-wind-turbine-device-pattern.md) §2.6): aktives Rauschen ohne `attach_random` → fail-loud; danach byte-kontinuierlich (tick-gekeyt). **Pin-neutral**: Demo ohne `cell`-Block → keine Zell-Telemetrie/-State → `EXPECTED_DEMO_*` unberuehrt. `cell=None` bit-genau wie heute (Regressions-Pin). `Accepted` 2026-06-17 (4b-Gates gruen). Trigger 024 aufgeloest. Out-of-Scope: Balancing, Sicherheitsabschaltung (M3), Zell-Chemie/OCV, per-Zelle-Telemetrie, voller RNG-Resume, Temperatur ([`ADR 0065`](0065-battery-thermal-telemetry-pattern.md)). |
8888
| 0067 | [Driver-unabhaengige Run-End-Naht + Partial-Run (Slice 040)](0067-run-end-seam-and-partial-run.md) | Accepted | 2026-06-17 | Slice 040 (Trigger 040, M7-Welle-1b-b-C2-Review-Befund #4). Additive Schaerfung von [`ADR 0049`](0049-replay-lifecycle-finalize-hook.md) §2.1/§7 ohne Supersedes (Pattern [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)). **§2.1** NEU `TickLoop.run_session()`-Kontextmanager garantiert `finalize()` im `finally` fuer jeden Konsumenten (Headless-Runner/Abnahme-CLI [`GG-MVP-003`](../../../spec/lastenheft.md#gg-mvp-003) ohne asyncio-Driver) — **kein** Auto-`completed` (braeuchte ein Tick-Budget, ADR 0049 §7). **§2.2** NEU idempotentes `mark_run_failed()`/`_run_failed`-Flag. **§2.3** `finalize()` ueberspringt bei Partial-Run den Diff (kein `replay_diff_status`, `partial_run`-Reject-Log statt irrefuehrendem `diverged`). **§2.4** `DemoTickLoopDriver._run_loop()` triggert `finalize()` im `finally` auf JEDEM Exit-Pfad (Natur-Terminierung/Failure/Cancel) + `mark_run_failed()` im Failure-Pfad; `stop()`-`finalize()` entfaellt (idempotent-redundant). `RunExecutionPort` um `mark_run_failed()` ergaenzt; `control_state`-Matrix ([`ADR 0039`](0039-run-control-and-status-tracking.md)) unberuehrt. `Accepted` 2026-06-17 (Slice-040-Gates gruen). Out-of-Scope: Auto-`completed`-Transition + `started_at`/`ended_at` (ADR 0049 §7); API-Replay-Surface = [Trigger 039](../planning/done/039-api-replay-trigger-surface.md). |
8989
| 0068 | [API-Replay-Bindung: persistentes `replay_of` + Reject (Slice 039 Phase A)](0068-api-replay-binding-persistence.md) | Accepted | 2026-06-17 | Slice 039 Phase A (Trigger 039, 1b-b-D-7). Additive Schaerfung von [`ADR 0049`](0049-replay-lifecycle-finalize-hook.md) §2.2 ohne Supersedes (Pattern [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)): die Referenzlauf-Bindung wird vom Runtime-Kwarg auf **persistent + API-getrieben** gehoben. **§2.1** NEU optionales `RunMetadata.replay_of: str\|None=None` + nullable Postgres-Spalte (Migration `0003_add_replay_of`; InMemory automatisch); **kein** `GG-TERM`-Preflight-Feld (Referenz, kein Konfig-Merkmal). **§2.2** `POST /runs` (`RunCreateRequest`, [`ADR 0045`](0045-http-api-request-strict-validation.md)) bekommt `replay_of`; unbekannte Referenz → **HTTP 422 `reference_run_not_found`** (Reject vor Save, nicht erst im `finalize()`-Preflight). **§2.3** `RunCreateResponse` + `RunDetailResponse` (`GET /runs/{id}`) exponieren `replay_of`. **§2.4** `finalize()`-Konsum der persistierten Bindung = **Phase B** (haengt am Run-Execution-Pfad; eigener Folge-Schritt). `Accepted` 2026-06-17 (Phase-A-Gates gruen). Schwester-Slice [`ADR 0067`](0067-run-end-seam-and-partial-run.md) (040). |
90-
| 0069 | [Multi-Run-Execution: per-Run-Driver + Scenario-Store (Provisional)](0069-multi-run-execution-and-scenario-store.md) | Provisional | 2026-06-18 | Architektur-Entwurf zum [`in-progress`-Slice-Plan](../planning/done/multi-run-execution-path.md): hebt die `TickLoopRegistry`/Driver ([`ADR 0039`](0039-run-control-and-status-tracking.md) Decision 13) additiv auf Multi-Run (kein Supersedes, [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)) und liefert den Run-Execution-Pfad fuer den `finalize()`-Konsum der persistierten `replay_of`-Bindung ([`ADR 0068`](0068-api-replay-binding-persistence.md) §2.4 = Phase B). **§2.1** Scenario-Store (A1) keyed by `scenario_hash` + `POST /scenarios`. **§2.2** `RunDriverRegistry` + bounded concurrency + Lifespan-Shutdown-all. **§2.4** `POST /runs/{id}/start`. **§2.5** Replay-Konsumnaht = Slice 039 Phase B (entsperrt 039+040). `Provisional` 2026-06-18 (S0, Owner-Mittragung); `Accepted` folgt bei der Implementierungs-Wellen-Closure (gates gruen) — dann werden die `Schaerfungen`-Spalten von [`ADR 0039`](0039-run-control-and-status-tracking.md)/[`ADR 0049`](0049-replay-lifecycle-finalize-hook.md)/[`ADR 0068`](0068-api-replay-binding-persistence.md) nachgezogen. |
90+
| 0069 | [Multi-Run-Execution: per-Run-Driver + Scenario-Store (Accepted)](0069-multi-run-execution-and-scenario-store.md) | Accepted | 2026-06-18 | Architektur-Entwurf zum [`Slice-Plan`](../planning/done/multi-run-execution-path.md): hebt die `TickLoopRegistry`/Driver ([`ADR 0039`](0039-run-control-and-status-tracking.md) Decision 13) additiv auf Multi-Run (kein Supersedes, [`ADR 0011`](0011-schaerfung-ohne-abloesung.md)) und liefert den Run-Execution-Pfad fuer den `finalize()`-Konsum der persistierten `replay_of`-Bindung ([`ADR 0068`](0068-api-replay-binding-persistence.md) §2.4 = Phase B). **§2.1** Scenario-Store (A1) keyed by `scenario_hash` + `POST /scenarios`. **§2.2** `RunDriverRegistry` + bounded concurrency + Lifespan-Shutdown-all. **§2.4** `POST /runs/{id}/start`. **§2.5** Replay-Konsumnaht = Slice 039 Phase B (entsperrt 039+040). `Provisional → Accepted` 2026-06-18 (Wellen-Closure: S1..S4 geliefert + frischer Welle-Review, gates gruen; Slice-Plan + Replay-Paar 039+040 nach `done/`). |
91+
| 0070 | [Scenario-Scheduled Device Commands: `commands`-Block + ScenarioCommandEngine (Provisional)](0070-scenario-scheduled-device-commands.md) | Provisional | 2026-06-18 | Architektur-Entwurf zum [`Slice-Plan`](../planning/in-progress/scenario-scheduled-device-commands.md) (Trigger 046): tick-genauer scenario-deklarierter Command-Zeitplan analog der Fault-Planung ([`ADR 0022`](0022-fault-injection-protocol.md)/[`ADR 0059`](0059-generic-scenario-fault-engine.md)), additiv ([`ADR 0011`](0011-schaerfung-ohne-abloesung.md), kein Supersedes). **§2.1** Top-Level `commands`-Block (mirror `faults`, Punkt-in-der-Zeit). **§2.2** `ScenarioCommand` + `Scenario.commands` (default leer, pin-neutral). **§2.3** `ScenarioCommandEngine` + `TickLoop`-Vor-Tick-Naht (scenario- vor Agent-Commands). **§2.4** `scenario_hash` deckt `commands` ab (Variante A, Decimal-Strings). **§2.5** nicht-idle E2E je SOLLTE-Geraet (Wind = `IGNORED`). `Provisional` 2026-06-18 (S0, Owner-Mandat Voll-Mechanismus); `Accepted` bei der Wellen-Closure (gates gruen). |
9192

9293
---
9394

docs/plan/planning/in-progress/carveouts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ daher die bewusste Nummern-Luecke §2.3..§2.6.
100100
| T-038 | Volle [`GG-TERM-002`](../../../../spec/lastenheft.md#gg-term-002)/003-Equality-Matrix (M7 liefert MVP-Preflight ueber 5 `RunMetadata`-Felder) | M7-Erbschaft | `Trigger-Gated` | M7-Welle-1b-a-D-6 | Compliance-/Audit-Bedarf ODER Multi-Plattform-/Multi-Adapter-Replay | [`038`](../open/038-gg-term-002-003-full-equality-matrix.md) |
101101
| T-039 | Oeffentliche API-Replay-Bedienung — **Phase A geliefert 2026-06-17** ([`ADR 0068`](../../adr/0068-api-replay-binding-persistence.md): `POST /runs` `replay_of` + Persistenz/Migration + 422-Reject + Response-Exposure); **Phase B geliefert 2026-06-18** (Multi-Run-Execution S4, [`ADR 0069`](../../adr/0069-multi-run-execution-and-scenario-store.md): `build_run_driver` verdrahtet persistiertes `replay_of``finalize()`-Diff gegen den Referenzlauf) | M7-Erbschaft | `Resolved` | M7-Welle-1b-b-D-7 | **Resolved 2026-06-18** (Phase B + Paar-Closure 039+040; Doc nach `done/`) | [`039`](../done/039-api-replay-trigger-surface.md) |
102102
| T-040 | Core-Run-End-Naht fuer `TickLoop.finalize()`**Geliefert 2026-06-17** ([`ADR 0067`](../../adr/0067-run-end-seam-and-partial-run.md): `run_session()`-Kontextmanager + `mark_run_failed()`/Partial-Run; `finalize()` auf jedem Driver-Exit-Pfad) | M7-Erbschaft | `Resolved` | M7-W1b-b-Review F4 / Welle 2 | **Resolved 2026-06-18** (Paar-Closure mit 039; Doc nach `done/`) | [`040`](../done/040-replay-finalize-headless-run-end-seam.md) |
103-
| T-046 | Command-getriebener Integration-E2E fuer die SOLLTE-Geraete (`apply_command`; die vier Szenario-Smokes fahren idle, generisches Command-Routing via Agents/Battery gedeckt) | SOLLTE-Geraete/Netz | `Trigger-Gated` | M8-Welle-2a..2d-Anti-Scope ([`M8-welle-2a.md`](../done/M8-welle-2a.md) §5) | scenario-scheduled-Command-Mechanismus im `devices`-Block ODER konkreter Bedarf an geraetespezifischer Command-Routing-Abdeckung jenseits Agents/Battery | [`046`](../open/046-command-driven-integration-e2e.md) |
103+
| T-046 | Command-getriebener Integration-E2E fuer die SOLLTE-Geraete **aktiviert 2026-06-18** als Scenario-Scheduled-Commands-Welle ([`ADR 0070`](../../adr/0070-scenario-scheduled-device-commands.md) `Provisional`, [`Slice-Plan`](scenario-scheduled-device-commands.md); S0 vollzogen, voller Mechanismus + 4 SOLLTE-E2E) | SOLLTE-Geraete/Netz | `In-Arbeit` | M8-Welle-2a..2d-Anti-Scope ([`M8-welle-2a.md`](../done/M8-welle-2a.md) §5) | aktiv (in-progress) | [`046`](../open/046-command-driven-integration-e2e.md) |
104104

105105
### 2.2 Begruendungen (per ID)
106106

0 commit comments

Comments
 (0)