ECS for Control: Round 4 Outlook
What the fourth round leaves open: recovery and the run-time error concept, asynchronous boundaries and transports that can fail, and the checks still to build.
Overview
Round 4 built the schedules and the definition: the same partitions on one schedule and on two, one per core, meeting only at their transports; the program's definition as an extracted artefact, every module declaring its kind; the review written down as rules, with two tools holding what the rules mark. The retrospective accounts for that delivery against the previous outlook, and this document is forward-only: what stands open, and what the round's results make reachable. It is a roadmap of the work ahead, not a statement of what the next round will deliver: a round takes up what it can carry, what it leaves stays on this list until a round takes it up, and the retrospective after each round says what was delivered. The list is long because the round opened more than it closed, which is what a round that builds the ground for others does.
One decision about the order is on record: the architectural questions come before the application rounds. The next round is an architecture round, opening on recovery with the run-time error concept and on the asynchronous boundaries – two subjects the first sections below hold, both revisited on what the schedules taught; how far into either it gets is the round's own business. The temporal domain, further observations and the framework step come after it (Later Rounds). The next round's programs will carry a new name; nothing here presumes their shape.
Recovery and Run-Time Errors
The two are one round's subject and two concepts, deliberately orthogonal in concept and in implementation: the run-time error concept says when and whether something is done about a detected condition; recovery on the store is one of its remedies, complete without it for a program whose only remedy is a restart, exercisable without it from a test. They meet at one edge, the decision to invoke.
Recovery on the store stands as specified: every recoverable unit has a token naming it, with a type, an owner and a place; a recovery subset is an authored set over tokens; each unit has a temporal-depth attribution – re-acquired from the world, reloaded from a capture, or rebuilt as baseline – which the store may carry per unit, or by structure, the store record divided along the attributions so that a group of Components shares one; recovery re-establishes this world and is delineated from migration, which builds a different one from inherited state, world identity deciding which is happening. The work is the doing:
-
exercise the snapshot – capture, compare, restore, against the specification, and the attribution's carrier decided with it – per unit, or by the divided store, where a capture is one record assignment per group and the tokens stay as they are;
-
verify it as the observations were verified – the test harness disturbs on purpose, every run's outcome predicted before it runs, the record compared character by character; a recovery cycle is as predictable as an adaptation cycle;
-
give creation its remaining phase – a self-test at the gate, through which every start passes, a recovery re-entry included should recovery take that route.
The run-time error concept is the map the previous outlook asked for and the round did not draw: where failures can occur, which detection family sees each – the per-event path of assertions, language checks and faults, or the observations over time – and what follows. The round drew one of its lines, at the transports: a failed precondition is a programming error and asserts; a full buffer met by the adapter is an operational condition and is observed. That line wants its general form – contract violation against operational condition, stated for every site where a check could stand – and with it the two questions still open: where an assertion is useful, and whether a System may assert at all, a failed assertion mid-tick ending the pass against run-to-completion. Above both families stands the decision layer: which findings restart, which invoke recovery, which degrade and continue – policy of the program, stated program-wide.
The round left the concept its first cases, every one from the two-core work:
-
an error inside the error path. The deferred-report handler runs at the same priority as the faults it reports, so a failure inside the report escalates to a hard fault, which is the present stance; a handler that must survive for recovery's sake needs a nesting policy – a second record per core, a minimal nested report, a halt – and the priority setting and the nesting behaviour are one decision, since lowering the priority alone lets a nested assertion pass silently;
-
hardware state survives a soft reset. A restart without a power cycle meets the devices as the previous run left them – spinlocks claimed, FIFOs and configurations set, timers running. Recovery's build and acquire stations need an inventory of what to reset explicitly, and the bottom of the ladder is the start that ignores every persisted store: a restart counter the resets do not clear, and a threshold;
-
the schedule with a dead or absent partner. A schedule whose partner never commits waits at the gate forever; a core that dies holding the error console leaves the other blocked at its next report; a schedule whose partner stops in steady state keeps reading the last complete value from the boundary's ports, the shared timer's timestamp the one fact it has. Each is a decision between waiting by design and a bounded wait with a stated error;
-
the double check on the hot path. The adapter meets a transport's precondition and the transport asserts the same precondition: two trust lines where the architecture wants one, and a per-build check policy is wanted that keeps creation's checks whatever the compiler's assertion switch says;
-
the locks after a reload. A re-initialisation must release what the previous run registered.
Two predictions go on record for the round. Heterogeneous snapshot and migration records per partition will tempt the one language feature the store has not used, record extension; the expected answer is the handle and socket idiom, not a type hierarchy in the store. And if recovery or error handling finds a module's kind valuable at run time, the kind alone – not its roles – may become an exported integer beside the declaration, at a small cost in code memory; the declaration in the comment stays the tools' source.
The Trust Line at Run Time
Infrastructure is brought up before world creation, asserts as it goes, and is trusted thereafter; world creation's checks apply to the authored world; and the line between the two is where a recovery re-entry would return to, should recovery re-run creation's later phases rather than re-value the store in steady state – world creation again, bring-up not; the concepts document draws the line on the steady-state side today, and which it is belongs to the recovery cases below. The round gave the line its first run-time form: the kernel is open from load until Run and locked from Run on, every station procedure refusing with an error, the lock per schedule and never released, since nothing after Run is entitled to configure.
The lock is one instance of a general shape – a resource open during creation and locked for steady state, set by the last station that configures it – and the shape has levels the round named and did not build:
-
a bring-up state opened at load and closed by
Run, which every configuring procedure of the library asserts: no configuration afterRun, as a refusal rather than a review item. The operational invariant that no System configures the infrastructure is held by review today; with the state it becomes a trap; -
the hardware's own locks where a chip has them, set as the driver's last configuration step and cleared by a reset alone; a software refusal in the driver where it has none;
-
the program's own resources – the heaps locked after creation, per core, so that an allocation in steady state fails its assertion rather than succeeding quietly;
-
an inventory at
Run: the bound devices from the handle tables and the claimed resources from a registry, reported once when the world is committed. The same inventory is recovery's device-reset list, read the other way.
Which resources lock, by which means, and whether a module locks itself at its last configuration step or exports an explicit lock are design questions the architecture round settles on the recovery cases above.
Asynchronous Boundaries
The concepts document carries one invariant marked for extension: asynchronous units are not Systems. The round built the machinery that would retire the mark. A schedule's crossings, declared once in the boundary, are seeded into its plan as a derived manifest – what arrives from elsewhere counts as produced – so that every construction check holds across the cut; an interrupt-driven unit is a producer paced by hardware rather than by a tick, and its productions are crossings into a schedule from a side that has no loop of its own. The external-producer declaration the previous outlook named is therefore the boundary declaration, used once more. What the architecture round evaluates:
-
the interrupt-driven unit as a System of a hardware-paced schedule, its port hosted in the store – tokened, declared, inside the recoverable picture – which demotes the rule that asynchronous state lives below the store from a must to a choice. Two wordings in the concepts document shift with it: an asynchronous unit is not this schedule's System rather than no System, and the far accessor of a hosted port is attributed through the declaration, so that every store access still belongs to a System;
-
overload – the world paces the unit through the transports already in place: the sampling port overwrites with the latest value, the ring clips and the clip is observed. Nothing new is needed for the rate mismatch;
-
interrupt affinity as device ownership – a device belongs to the schedule whose Systems use it, its interrupt on that schedule's core; the exception priority scheme and the software-interrupt vectors are authored facts of the resources module, and a library module takes its priority as an installation parameter, as the kernel's tick does;
-
the binding made stateless again. Today an asynchronous binding holds transfer state by sanctioned exception – a single-producer single-consumer queue between the interrupt and the System's call, each item consumed once, never a cache. With the port in the store the binding holds nothing, and the exception goes.
The transport protocols were built for exactly this counterparty, an unsynchronised peer; the round proved them between two cores, and the interrupt is the case where the peer has no clock of its own.
Transports That Can Fail
Every observation the round records is a by-product of a precondition check: a put that does not fit, a get that finds nothing. That form covers capacity and nothing else. A sampling port over a noisy bus, a timeout, a failed checksum present the adapter with perfect data and a failed transfer, and no precondition can see them. The transport must report: an event the caller cannot foresee comes back through the transport's interface – a result parameter, never a function result, so that the call stays a statement – and the adapter, the one caller of every transport, takes the observation as it takes the clip today. The concepts document already says so in general terms; the round that builds the first failing transport gives it the form.
The case arrives with the second box. Between two CPUs with no shared memory the port lives twice, one replica per side, and the bridging machinery keeps them synchronised – serialise, send, deserialise; a bus adds latency and jitter, and it can lose. Loss and staleness then become event sources of their own, surfacing as a freshness or validity value beside the payload – more interface data, decided at design time, never a change of structure. The receiving side of such a link is hardware-paced, which is where this item meets the asynchronous boundaries; and what the impacted System does with a stale or absent value is the error concept's decision layer. One rule stays as it is: an adapter fronts one transport, and a System that needs two values of one instant needs one port carrying a record – coherence is the producer's responsibility, not something an adapter arranges.
Checks to Build
The review guide is a draft until it is published with the library, and the tools are in beta until then. What the next rounds add to them:
-
readings that graduate. Some check-points the reviewer holds today may move: into a construct, so that the guide reads a form instead of an intent; into the gate, where the kernel can refuse; into the language, where a type or a mode settles it. Each edition of the implementation document and the guide is revised together, and a rule that moved is written where it now lives;
-
readings that become tool-held. The guide marks what the tools decide and what they record; a reader-held check-point becomes the tools' when a program motivates it and the reading can be stated over the sources alone. One check-point the guide defines and nothing yet checks: a token no manifest names;
-
the generator's publication. The skeleton generator is a tool of the round, not published with it. It goes out with the tools: the generated skeletons beside the programs, the header a skeleton carries, and the adapter form for a single transport instance, which an authored definition cannot choose because it has no store yet;
-
the program's singletons across several binaries. One boundary module for a program with several schedules, at most one of each program-level definition module: the reading assumes one binary. Programs across MCUs revisit it;
-
the identifiers. Dense and renumberable until the guide is published; from then on never reused, a retired check-point keeping its number as a tombstone;
-
the reference implementations and the allow-lists move into the library with the tools at the release, and the transports' bodies are then compared against the library's reference rather than the tools' copy. Where in the library that material lives, one directory for the reference implementations, the templates and the allow-lists, since they move together, and how the tools find it, is a concept to settle before the release: derived from the build where there is one, since the linker map file names where every linked library module lies, and configured where there is none, the generator reading an authored file with no build behind it.
Later Rounds
-
The temporal domain. Everything the round builds is denominated in ticks and operations; what the schedules forced is settled – a quantity in ticks belongs to the schedule whose tick counts it, a crossing value is in the consumer's tick – and the rest stands where the previous outlook put it: producer rates as declared facts; deadlines and budgets, how late a value may be, how much of a tick a System may spend, what happens when the answer is too much; the whole-program view of periods and rates against the controlled system's demands. The kernel's run-time instrumentation is the measuring side, and the round's closing measurement is the baseline. One question the round withdrew from the checks belongs here: a System that writes to a terminal can block a pass on a full FIFO, and whether it does is depth, baud rate and bytes per pass – a timing budget, not a rule.
-
Programs across several MCUs. The second box is the concept's claim and the schedules document's declared gap: one token space across several binaries, a program's definition spanning them, the transports replicated over a link (Transports That Can Fail). Only a running implementation gives it a sound basis.
-
Space partitioning. Partition privacy is guaranteed by the compiler and the tools; a memory protection region per schedule would enforce it at run time, the natural next item on the partitioned architecture. No global major frame is wanted: across schedules the model is quasi-synchronous by design, within a schedule the plan's order is the frame. Interference between the cores on the shared bus and peripherals is the certification-style question and lies outside the architecture.
-
Observations, continued. The apparatus is built and runs across cores; its instruments and facilities stand where they were: the tick monitor as an observation-based instrument feeding the same distillation it bypasses, the heartbeat back in its reporting role, queues, ports and drivers as watched facilities when the programs grow.
-
Toward lib/v4.0. The framework step is a delineation first and a staggered release second, each stage its own release with its own change note: the library for the RP2350 with the evaluation programs as its examples, the curated programs ported and target-verified, then the same per further MCU family. The gate is pulled when seen fit, a judgement rather than a date, and the kernel's surface is no longer among its considerations: the kernel is complete and stable for the functionality it has, and what the temporal round adds to it are version bumps after a release. What the release carries beyond the modules is now known: the review guide, the two tools and the generator, the allow-lists file and the kinds' reference implementations. Throughout, programs written against the current library remain unaffected.
See Also
- ECS for Control: Concepts
- ECS for Control: Implementation
- ECS for Control: Schedules
- ECS for Control: Review Guide
- ECS for Control: Round 3 Retrospective
- ECS for Control: Round 3 Outlook
- ECS for Control: Observations – Concepts
Last updated: 3 November 2026