You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Start gate: DC7 #1440 is complete (merged via PR #1484). Met.
Goal
Give the four domains whose legacy answer is already terminal their own pending protocol state and
operation seam, so the pass — not Activity, not HostPending, not the drain — owns their
lifecycles.
This issue does not delete the legacy protocol and does not move any host onto App::run_pass.
Both belong to S6.
Read this first: the revised S2 row is stale
The #1397 amendment's revised S2 says S2 adds OperationToken, Capability, named external facts,
and one intent/effect/outcome enum per domain with named bounded slots. All of that has since
landed — DC2 #1435 / PR #1468 (tokens, capabilities, ExternalFacts) and DC3 #1436 / PR #1475
(nine domain protocols, EffectSlots / OutcomeSlots). DC5 #1438 / PR #1481 added the fourteen-stage
coordinator, DC6 #1439 / PR #1483 the LegacyAdapter, DC7 #1440 / PR #1484 the conformance gate.
The original S2 row — "protocol slot table (non-storage rows); delete the four hand lists" — is also
not implementable as written: the amendment explicitly bans a global generated table
("One global table must not own all domain policy"), and the four hand lists cannot be deleted while
the production hosts still drain them, which is S6's cutover.
What is genuinely left is the sentence in the amendment that nothing has yet acted on:
Each domain owns its pending state.
Six domains still do not. device_core_conformance.rs names them in its own source — "the legacy
half, for the six domains without a machine": Navigator, Recorder, Settings, DFU, Bond,
StorageInfo. Their pending state sits in Activity one-shot slots, HostPending, and two App
fields, and stages 7, 9 and 11 of the pass are empty because of it.
This issue takes four of the six. The scope line is below and it is not arbitrary.
The pass order and the two delivery rules: firmware/obc-app/src/device_core/pass.rs module docs.
The legacy mapping for every variant: firmware/obc-app/src/device_core/migration.rs.
What the legacy side still owns and who deletes it: LegacyOwned in firmware/obc-app/src/device_core/compat.rs.
The scope line
LegacyReply::ALL in compat.rs lists the seven legacy classes that have a terminal HostEvent answer. Those seven classes belong to exactly four domains:
Eight of the eighteen command classes and nine of the fifteen events.
#1433 §4.3 states the rule this follows: a domain that cannot validate a token cannot be the owner
of an outcome. These four can validate one today, on the legacy transport, because the
legacy protocol already delivers their terminal answer. The two domains left out cannot:
Building a machine for Bond or Recorder in this slice would be a state machine with nothing to
validate and no answer to consume — the speculative structure this repository bans.
Current code
Everything below is at origin/develop after PR #1484.
The pending state the eight classes read (this is what moves):
Activity: nav_request, nav_cancel, detour_request, detour_commit, detour_cancel, dfu_request, card_scan_request — seven fields with a take_* / has_* / pending_* / clear_* family each (firmware/obc-app/src/activity.rs:246–880).
HostPending: settings_rev, retry_at_ms, persist, plus PersistState, retry_deadline_reached, SETTINGS_RETRY_BACKOFF_MS and seven methods
(firmware/obc-app/src/host.rs:500–685).
App::card_free_bytes, written straight from apply_event.
The freeze edges note_plan_started / note_plan_ended, fired from inside drain_host_command's PlanRoute / PlanDetour / CancelRoutePlan / CancelDetour arms.
App::on_nav_planned / on_detour_planned / on_detour_committed in apply_event.
What already exists and must be used, not rebuilt:
navigator.rs (158 lines), settings.rs:2043–2090, dfu.rs:249–300, device_core/storage_info.rs — vocabulary only, no machine. navigator.rs's own header says the
machine "arrive[s] in later slices".
CatalogState::admit_intent / next_effect / apply_outcome
(firmware/obc-app/src/catalog_state.rs:721–776) — the precedent for the operation seam. Copy
its shape: the seam lives beside the domain's existing state, owns the token and the in-flight
count, and nothing else.
compat.rs's navigator_row, settings_row, dfu_row, storage_info_row — the translation each
effect already has.
The nine EffectSlots / OutcomeSlots fields. No new slot field is needed.
The empty stages:stage_recorder (position only), stage_settings (records and returns), stage_platform (records and returns), stage_navigator (handles ActiveRouteRemoved and the
activation deferral, nothing else).
Locked design
Each of the four domains gets one operation seam: admit_intent, next_effect, apply_outcome, on the type that already holds that domain's state. No new module for
Settings, DFU or StorageInfo. Navigator's machine lives in the existing navigator.rs.
The seam is the only owner of that domain's pending state. After this slice a rider request
exists in exactly one place. Two copies is the defect this whole epic exists to remove; do not
leave the Activity slot behind "for the drain".
peek_host_command and drain_host_command keep their eight arms, and each becomes a
one-line delegation: ask the domain for its next effect, translate it with the existing compat.rs row. Translation only — no policy in the arm.
Navigator owns the planning lifecycle: Idle → Planning → PreviewReady → Committing → Active,
or Failed. It owns cancellation, replacement, and late-answer rejection. It keeps the two rules
the current code hides in the drain:
Navigator becomes the single writer of RerouteFreeze.reroute_freeze.rs, arena_gate.rs
and link_gate.rs stay exactly as they are — S5 deletes them and derives the freeze from CoreMode. S2 replaces four scattered edge calls with one call site driven by a state change.
App::on_nav_planned and friends stop being protocol handlers. What survives is the UI's
reaction to a Navigator state change.
No new deferred connection unless a stage genuinely needs to reach backwards. The wake ratchet
in the conformance replay is the check.
Target files
firmware/obc-app/src/navigator.rs # + the machine
firmware/obc-app/src/settings.rs # + the seam, absorbing HostPending's persist half
firmware/obc-app/src/dfu.rs # + the seam
firmware/obc-app/src/device_core/storage_info.rs # + the seam and the free-bytes level
firmware/obc-app/src/device_core/pass.rs # stages 8, 9, 11 advance; stage 4 gains three intents
firmware/obc-app/src/device_core/connections.rs # + the ui→navigator / ui→dfu / ui→storage intents
firmware/obc-app/src/activity.rs # − seven slots and their method families
firmware/obc-app/src/host.rs # − the persist half of HostPending
firmware/obc-app/src/app.rs # eight drain/peek arms become delegations
host/obc-host-core/tests/device_core_conformance.rs
host/obc-host-core/tests/device_core_corpus/mod.rs
Implementation steps
Add NavigatorMachine's state and seam in navigator.rs, with its TokenSource<NavigatorTag>.
Port the annihilation and per-family-release rules with their tests before touching Activity.
Wire stage 8: take the UI's NavigatorIntent, advance, emit at most one NavigatorEffect into effects.navigator, consume outcomes.navigator at stage 1. Keep the existing ActiveRouteRemoved and activation handling.
Make Navigator drive RerouteFreeze from its own transitions, and delete the four note_plan_started / note_plan_ended calls in drain_host_command.
Move the settings persist handshake out of HostPending into settings.rs behind SettingsIntent / SettingsEffect::PersistRevision / SettingsOutcome. HostPending is left
holding only the store-changed counter; fold it into App if nothing else remains.
Add the DFU seam in dfu.rs: the single most-recent-wins phase slot, the scan and install
admissions, and the deferral the remote BLE door already relies on
(App::open_remote_dfu_check).
Add the StorageInfo seam: the request level and the free-bytes result, replacing App::card_free_bytes and Activity::card_scan_request.
Rewrite the eight peek_host_command / drain_host_command arms as delegations through the
existing compat.rs rows.
Delete the seven Activity slots and their method families, and the HostPending persist half.
Extend the conformance harness: serve_mailbox's comment and its refusal set now cover two
domains, not six; the_pass_owns_the_classes_it_took_over gains the eight classes.
Re-measure every resource value against the DC7 baseline and itemize any increase.
Deletions
This slice must remove, in the same PR that replaces them:
Activity::nav_request, nav_cancel, detour_request, detour_commit, detour_cancel, dfu_request, card_scan_request and every take_* / has_* / pending_* / clear_* method
reaching them.
The four note_plan_started / note_plan_ended call sites inside drain_host_command.
The protocol halves of App::on_nav_planned, on_detour_planned, on_detour_committed.
LOC honesty.#1397 §3.1 budgets Area C at −195 production lines. That number described the
generated slot table the amendment cancelled. This slice is expected to be roughly flat to +250:
it moves state rather than removing a mechanism, and the deletion — the four hand lists, the legacy
enums, compat.rs, migration.rs, feeders.rs — lands at S6. A PR that claims a net deletion here
is probably leaving a second copy of some pending state behind. Update the epic's Area C row rather
than bending the slice to it.
Required tests
Port before deleting, per the epic's rule. Focused, not exhaustive.
A plan answer arriving after cancellation changes nothing.
A plan answer arriving after a replacement request changes nothing.
A route plan that completes after the active route changed is refused.
A detour without a path is a failure, not an absent capability; a device without NavigatorCapabilities::plan_detour never enters the planning path at all.
Cancelling a detour clears the preview polyline.
Settings
No emit while the rider is inside the settings subtree; one emit on exit.
A stale ack (a newer edit bumped the revision) does not clear the newer state.
A failed write keeps the revision dirty, backs off, and retries once.
A host that drains but never acks parks without re-emitting.
DFU
The single phase slot is most-recent-wins; the remote door defers instead of replacing.
A scan failure and an install failure both reach the rider's terminal card.
StorageInfo
The request is an idempotent refresh; CardScanned { free_bytes: None } is a reported failure,
not a retry loop.
Conformance (obc-host-core)
All 20 DC1 scenarios × 5 runners stay green, with the same disposition table: 1 corrected row
and 3 accepted LegacyOwned::ObjectNamespace cells. A new differing cell is a blocking failure.
the_pass_owns_the_classes_it_took_over covers the eight classes: under a DeviceCore runner they
never pend on drain_host_commands, and the same rider request is served as a typed effect.
Every LegacyOwned row still names a later owner. No row is retired by this slice — PlannerPacing and PlannerRelease stay with S6, because the legacy host still paces and releases
the planner itself.
Resource limits
Baseline is DC7's, recorded in PR #1484 and firmware/tools/resource_baseline.json. Report every
value; itemize any increase.
Item
Baseline
Rule
size_of::<App>() (target)
50,904 B
Must not grow. Seven Activity slots leave; four token generations arrive.
Board resident (.bss + .data)
304,808 B
Must not grow.
size_of::<EffectSlots>()
160 B
Unchanged — no new slot field.
size_of::<OutcomeSlots>()
224 B
Unchanged.
Largest domain effect
56 B (NavigatorEffect)
Ceiling 96 B.
Largest domain outcome
96 B (DfuOutcome)
Ceiling 96 B.
Flash
1,448,904 B
Ceiling 1,524,676 B.
Residual main stack
54,616 B
Floor 38,808 B.
Guarded poll frame
9,792 B
Limit 12,288 B.
Main task future
1,100 B
Limit 8,192 B.
Scratch arena (USB / render / nav)
131,072 / 131,072 / 97,344 B
Unchanged.
Replay wake profile
366 passes, 6 immediate wakes
Must not rise.
Mean / worst pass time
0.215 µs / 5.50 µs
Ratcheted in the committed test.
No allocator, no new task, no combined Effect/Outcome/Intent enum, no event bus (#1433 §14, §16).
Verification
obc test -p obc-app
obc test -p obc-host-core
obc test fixtures -p obc-host-core
cargo clippy -p obc-app -p obc-host-core --all-targets -- -D warnings
obc check fmt device board
Run obc suites check if test sources or the registry move. obc test full / obc check full only
if CONTRIBUTING.md requires it — this change is confined to obc-app and its conformance tests.
Explicitly out of scope
S5: CoreMode, and deleting reroute_freeze.rs, arena_gate.rs, link_gate.rs's search arm, transfer_screen_up, or the two *_arena_precondition derivations.
S6: moving any host onto App::run_pass; deleting the four hand lists, HostCommand, HostEvent, HostLoop, HostPass, compat.rs, migration.rs or feeders.rs; the storage rows
(RescanStore, the three deletes, the two stamps, FinishTrack); Recorder's and Bond's machines.
Navigator, SettingsMachine, DfuState and StorageInfo each own their pending state, their OperationToken, and one admit_intent / next_effect / apply_outcome seam.
Pass stages 8, 9 and 11 advance real machines; stage 4 delivers their intents in the same pass.
No rider request for those eight classes exists in two places.
The eight peek_host_command / drain_host_command arms contain translation only.
Every listed deletion is in the same PR as its replacement.
DRAIN_ORDER, class(), HostCommand and HostEvent are unchanged.
The DC7 conformance matrix is green with the same disposition table and no new differing cell.
Every resource value is recorded and within its limit, with any increase itemized.
LegacyOwned::ALL still resolves; the rows this slice does not retire say why.
The report lists the exact checks run and the ones deliberately omitted.
Open questions for the epic owner
Should the three small domains ship here or at S6? Only Navigator is a hard precondition for
S5. Settings, DFU and StorageInfo are the same shape and are cheap to do now, but if capacity is
short they can be deferred to S6 without blocking anything. Deferring means their pending state
stays hand-unrolled through S5.
One PR or two? Navigator alone is the load-bearing half and the riskiest. A Navigator PR
followed by a "three small seams" PR is the safer split.
The epic's Area C LOC row (−195) should be rewritten to describe this slice, and the amendment's
revised-S2 paragraph should be replaced with this scope — it currently describes work that DC2 and
DC3 delivered.
store_writable cannot come back down (pass.rs stage 12): ExternalFacts has no unmount
fact, so a pulled card leaves catalog mutation asserted. That is a fact-vocabulary gap, not a
stage-12 gap. Does it belong here, in S5 with CoreMode, or in S6 with the store executor?
Parent epic: #1397
Program tracker: #1448
Program position: App core, revised S2
Start gate: DC7 #1440 is complete (merged via PR #1484). Met.
Goal
Give the four domains whose legacy answer is already terminal their own pending protocol state and
operation seam, so the pass — not
Activity, notHostPending, not the drain — owns theirlifecycles.
This issue does not delete the legacy protocol and does not move any host onto
App::run_pass.Both belong to S6.
Read this first: the revised S2 row is stale
The #1397 amendment's revised S2 says S2 adds
OperationToken,Capability, named external facts,and one intent/effect/outcome enum per domain with named bounded slots. All of that has since
landed — DC2 #1435 / PR #1468 (tokens, capabilities,
ExternalFacts) and DC3 #1436 / PR #1475(nine domain protocols,
EffectSlots/OutcomeSlots). DC5 #1438 / PR #1481 added the fourteen-stagecoordinator, DC6 #1439 / PR #1483 the
LegacyAdapter, DC7 #1440 / PR #1484 the conformance gate.The original S2 row — "protocol slot table (non-storage rows); delete the four hand lists" — is also
not implementable as written: the amendment explicitly bans a global generated table
("One global table must not own all domain policy"), and the four hand lists cannot be deleted while
the production hosts still drain them, which is S6's cutover.
What is genuinely left is the sentence in the amendment that nothing has yet acted on:
Six domains still do not.
device_core_conformance.rsnames them in its own source — "the legacyhalf, for the six domains without a machine": Navigator, Recorder, Settings, DFU, Bond,
StorageInfo. Their pending state sits in
Activityone-shot slots,HostPending, and twoAppfields, and stages 7, 9 and 11 of the pass are empty because of it.
This issue takes four of the six. The scope line is below and it is not arbitrary.
Source of truth
firmware/obc-app/src/device_core/pass.rsmodule docs.firmware/obc-app/src/device_core/migration.rs.LegacyOwnedinfirmware/obc-app/src/device_core/compat.rs.The scope line
LegacyReply::ALLincompat.rslists the seven legacy classes that have a terminalHostEventanswer. Those seven classes belong to exactly four domains:RoutePlan,DetourPlan,DetourCommitPlanRoute,PlanDetour,CommitDetour,CancelRoutePlan,CancelDetourNavPlanned,DetourPlanned,DetourCommittedSettingsWritePersistSettingsSettingsPersisted,SettingsPersistFailedDfuScan,DfuInstallDfuDfuScanned,DfuInstallBegan,DfuInstallFailedCardScanScanCardFreeCardScannedEight of the eighteen command classes and nine of the fifteen events.
#1433 §4.3 states the rule this follows: a domain that cannot validate a token cannot be the owner
of an outcome. These four can validate one today, on the legacy transport, because the
legacy protocol already delivers their terminal answer. The two domains left out cannot:
LegacyOwned::BondAck: the legacy bond removal is confirmed by a link-status fact, notby a reply. Its row already names EPIC — App core: one mode machine, one card scheduler, one frame pass #1397 S6 as the owner.
LegacyOwned::RecorderJournalandLegacyOwned::RideCloseAck: the legacy hostwrites samples and checkpoints off-protocol and answers a close with a catalog re-feed. Both rows
name EPIC — App core: one mode machine, one card scheduler, one frame pass #1397 S6. PR DC7: close the DeviceCore Phase 1 conformance gate #1484 removed a
UiRuntime→Recorderconnection precisely because provisioningfor a lifecycle Recorder does not own destroyed the rider's ride close. Do not re-add it here.
Building a machine for Bond or Recorder in this slice would be a state machine with nothing to
validate and no answer to consume — the speculative structure this repository bans.
Current code
Everything below is at
origin/developafter PR #1484.The four hand lists (unchanged since #800):
HostCommand::DRAIN_ORDERfirmware/obc-app/src/host.rs:262HostCommand::class()firmware/obc-app/src/host.rs:284App::peek_host_commandfirmware/obc-app/src/app.rs:3286App::drain_host_commandfirmware/obc-app/src/app.rs:3320retention_expiry_commandThe pending state the eight classes read (this is what moves):
Activity:nav_request,nav_cancel,detour_request,detour_commit,detour_cancel,dfu_request,card_scan_request— seven fields with atake_*/has_*/pending_*/clear_*family each (firmware/obc-app/src/activity.rs:246–880).HostPending:settings_rev,retry_at_ms,persist, plusPersistState,retry_deadline_reached,SETTINGS_RETRY_BACKOFF_MSand seven methods(
firmware/obc-app/src/host.rs:500–685).App::card_free_bytes, written straight fromapply_event.note_plan_started/note_plan_ended, fired from insidedrain_host_command'sPlanRoute/PlanDetour/CancelRoutePlan/CancelDetourarms.App::on_nav_planned/on_detour_planned/on_detour_committedinapply_event.What already exists and must be used, not rebuilt:
navigator.rs(158 lines),settings.rs:2043–2090,dfu.rs:249–300,device_core/storage_info.rs— vocabulary only, no machine.navigator.rs's own header says themachine "arrive[s] in later slices".
CatalogState::admit_intent/next_effect/apply_outcome(
firmware/obc-app/src/catalog_state.rs:721–776) — the precedent for the operation seam. Copyits shape: the seam lives beside the domain's existing state, owns the token and the in-flight
count, and nothing else.
compat.rs'snavigator_row,settings_row,dfu_row,storage_info_row— the translation eacheffect already has.
EffectSlots/OutcomeSlotsfields. No new slot field is needed.The empty stages:
stage_recorder(position only),stage_settings(records and returns),stage_platform(records and returns),stage_navigator(handlesActiveRouteRemovedand theactivation deferral, nothing else).
Locked design
admit_intent,next_effect,apply_outcome, on the type that already holds that domain's state. No new module forSettings, DFU or StorageInfo. Navigator's machine lives in the existing
navigator.rs.exists in exactly one place. Two copies is the defect this whole epic exists to remove; do not
leave the
Activityslot behind "for the drain".peek_host_commandanddrain_host_commandkeep their eight arms, and each becomes aone-line delegation: ask the domain for its next effect, translate it with the existing
compat.rsrow. Translation only — no policy in the arm.DRAIN_ORDER,class(),HostCommandandHostEventare not touched. The wire vocabularyis unchanged (EPIC — Device core: one product kernel, typed effects, thin platform executors #1433 §16). All four lists die at S6 with the protocol.
Idle → Planning → PreviewReady → Committing → Active,or
Failed. It owns cancellation, replacement, and late-answer rejection. It keeps the two rulesthe current code hides in the drain:
(
HostCommand::CancelRoutePlandocs, nav: resumable planner + on-device planning screen (spinning compass) + phase instrumentation #499);RerouteFreeze.reroute_freeze.rs,arena_gate.rsand
link_gate.rsstay exactly as they are — S5 deletes them and derives the freeze fromCoreMode. S2 replaces four scattered edge calls with one call site driven by a state change.App::on_nav_plannedand friends stop being protocol handlers. What survives is the UI'sreaction to a Navigator state change.
in the conformance replay is the check.
Target files
Implementation steps
NavigatorMachine's state and seam innavigator.rs, with itsTokenSource<NavigatorTag>.Port the annihilation and per-family-release rules with their tests before touching
Activity.NavigatorIntent, advance, emit at most oneNavigatorEffectintoeffects.navigator, consumeoutcomes.navigatorat stage 1. Keep the existingActiveRouteRemovedand activation handling.RerouteFreezefrom its own transitions, and delete the fournote_plan_started/note_plan_endedcalls indrain_host_command.HostPendingintosettings.rsbehindSettingsIntent/SettingsEffect::PersistRevision/SettingsOutcome.HostPendingis leftholding only the store-changed counter; fold it into
Appif nothing else remains.dfu.rs: the single most-recent-wins phase slot, the scan and installadmissions, and the deferral the remote BLE door already relies on
(
App::open_remote_dfu_check).App::card_free_bytesandActivity::card_scan_request.peek_host_command/drain_host_commandarms as delegations through theexisting
compat.rsrows.Activityslots and their method families, and theHostPendingpersist half.serve_mailbox's comment and its refusal set now cover twodomains, not six;
the_pass_owns_the_classes_it_took_overgains the eight classes.Deletions
This slice must remove, in the same PR that replaces them:
Activity::nav_request,nav_cancel,detour_request,detour_commit,detour_cancel,dfu_request,card_scan_requestand everytake_*/has_*/pending_*/clear_*methodreaching them.
HostPending::settings_rev,retry_at_ms,persist;PersistState;retry_deadline_reached;SETTINGS_RETRY_BACKOFF_MS;note_settings_edited;reset_settings_clean;settings_persist_ready;drain_settings_persist;on_settings_persisted;on_settings_persist_failed;arm_settings_save.App::card_free_bytesand itsapply_eventarm.note_plan_started/note_plan_endedcall sites insidedrain_host_command.App::on_nav_planned,on_detour_planned,on_detour_committed.LOC honesty. #1397 §3.1 budgets Area C at −195 production lines. That number described the
generated slot table the amendment cancelled. This slice is expected to be roughly flat to +250:
it moves state rather than removing a mechanism, and the deletion — the four hand lists, the legacy
enums,
compat.rs,migration.rs,feeders.rs— lands at S6. A PR that claims a net deletion hereis probably leaving a second copy of some pending state behind. Update the epic's Area C row rather
than bending the slice to it.
Required tests
Port before deleting, per the epic's rule. Focused, not exhaustive.
Navigator
NavigatorCapabilities::plan_detournever enters the planning path at all.Settings
DFU
StorageInfo
CardScanned { free_bytes: None }is a reported failure,not a retry loop.
Conformance (
obc-host-core)and 3 accepted
LegacyOwned::ObjectNamespacecells. A new differing cell is a blocking failure.the_pass_owns_the_classes_it_took_overcovers the eight classes: under a DeviceCore runner theynever pend on
drain_host_commands, and the same rider request is served as a typed effect.traces obc-reader: map reader can panic/misread on malformed .obcm (32-bit usize overflow + unvalidated chunk_id) #1, obc-pack/obc-reader: features past MAX_FEAT_PTS=2048 silently truncated (no packer cap) #2, obc-pack: silent narrowing casts corrupt out-of-range fields (Python oracle used to abort) #5, obc-app: unchecked u32 clock add in elevation cursor idle timer (overflow near millis wrap) #6, Large per-call/per-frame stack buffers risk MCU stack overflow (decode_chunk, draw_route, elevation_profile) #10 and obc-app: TrackSink::record is infallible — no error path for SD write failures #11 now run against real machines rather than the legacy path, and
must be re-read for whether their assertion still describes the situation.
the_conformance_replay_wake_profile_and_pass_costre-pinned.LegacyOwnedrow still names a later owner. No row is retired by this slice —PlannerPacingandPlannerReleasestay with S6, because the legacy host still paces and releasesthe planner itself.
Resource limits
Baseline is DC7's, recorded in PR #1484 and
firmware/tools/resource_baseline.json. Report everyvalue; itemize any increase.
size_of::<App>()(target)Activityslots leave; four token generations arrive..bss + .data)size_of::<EffectSlots>()size_of::<OutcomeSlots>()NavigatorEffect)DfuOutcome)No allocator, no new task, no combined
Effect/Outcome/Intentenum, no event bus (#1433 §14, §16).Verification
Board build and resource gate:
cd firmware/obc-fw-nrf54l cargo build --release --locked python3 ../tools/resource_guard.py board --profile default --elf target/thumbv8m.main-none-eabihf/release/obc-fw-nrf54l cargo build --release --locked --features resource-report python3 ../tools/resource_guard.py report --profile default --elf ...Run
obc suites checkif test sources or the registry move.obc test full/obc check fullonlyif
CONTRIBUTING.mdrequires it — this change is confined toobc-appand its conformance tests.Explicitly out of scope
CoreMode, and deletingreroute_freeze.rs,arena_gate.rs,link_gate.rs's search arm,transfer_screen_up, or the two*_arena_preconditionderivations.App::run_pass; deleting the four hand lists,HostCommand,HostEvent,HostLoop,HostPass,compat.rs,migration.rsorfeeders.rs; the storage rows(
RescanStore, the three deletes, the two stamps,FinishTrack); Recorder's and Bond's machines.Definition of done
OperationToken, and oneadmit_intent/next_effect/apply_outcomeseam.peek_host_command/drain_host_commandarms contain translation only.DRAIN_ORDER,class(),HostCommandandHostEventare unchanged.the DFU single-slot rule) have tests on the new machines.
LegacyOwned::ALLstill resolves; the rows this slice does not retire say why.Open questions for the epic owner
S5. Settings, DFU and StorageInfo are the same shape and are cheap to do now, but if capacity is
short they can be deferred to S6 without blocking anything. Deferring means their pending state
stays hand-unrolled through S5.
followed by a "three small seams" PR is the safer split.
revised-S2 paragraph should be replaced with this scope — it currently describes work that DC2 and
DC3 delivered.
store_writablecannot come back down (pass.rsstage 12):ExternalFactshas no unmountfact, so a pulled card leaves catalog mutation asserted. That is a fact-vocabulary gap, not a
stage-12 gap. Does it belong here, in S5 with
CoreMode, or in S6 with the store executor?