RFC-0043.1: @@[async(casing: false)] — optional casing/gate
- Status: Accepted — implemented (branch
release/4.6.1; not yet released) - Author: Mark Truluck mark.truluck@cogiton.com
- Created: 2026-07-06
- Amends: RFC-0043 (
@@[async]— single-driver gate via layered casing/machine)
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.
Summary
RFC-0043 emits every @@[async] system as a two-class layered structure: a
public casing that guards external entry
with the E703
single-driver gate, wrapping a private machine (_<Name>Machine). This amendment
adds an opt-out parameter to the attribute:
@@[async]— unchanged and remains the default. Emits the layered casing/machine with theE703gate.@@[async(casing: false)]— the flat form. Emits the async system as a single public class carrying the async dispatch core (theinit()boundary and await plumbing) but with no casing, noE703gate, and no_<Name>Machinesplit.
The gate stays the default because it is the safety guarantee; the flat form is an explicit opt-out for systems that are single-driver by construction (or whose host already serializes calls) and do not want the guard.
Motivation
The casing exists to enforce the single-driver contract — one external dispatch
in flight at a time, E703 on re-entrant entry. That is the right default. But
it is not free:
- it adds a second class (
_<Name>Machine) that user code MUST NOT name directly; - it adds a construction hop (casing → machine factory — see issue #167);
- it puts the busy/in-flight gate on every interface call.
Some async systems do not need the gate: a self-driving service kernel that is the sole driver of its own loop, or a machine embedded inside a host that already serializes calls. For those, the layering is overhead and an extra public surface. This amendment lets the author drop it deliberately, while keeping the safe gated form as the default nobody has to think about.
The contract
Grammar
@@[async] gains an optional keyword argument:
@@[async] // gated (default) — identical to RFC-0043
@@[async(casing: true)] // gated — explicit, equal to the default
@@[async(casing: false)] // flat — no casing, no gate
The argument is a single casing: <bool> pair. casing defaults to true, so
@@[async] and @@[async(casing: true)] are exactly equivalent. Whitespace
around the : is not significant.
@@[async(casing: false)] is the first attribute to use the colon-keyed
argument form (key: value); existing attributes use positional args
(@@[persist(string)], @@[target("python_3")], @@[create(name)]). The
keyed-argument parser introduced here SHOULD be written to be reusable by
future @@[...] options, not special-cased to casing.
Emission — flat form
When casing: false, framec MUST NOT emit the casing or the
_<Name>Machine rename. It emits a single class under the user-declared
system name, containing the async dispatch core RFC-0043 already produces for
the machine (the flipped-is_async dispatch chain, the await/.await/
co_await boundary, and init()). Specifically, the flat form:
- MUST NOT emit the busy/in-flight fields or the
E703gate; - MUST keep
init()and the deferred$>cascade boundary (construction does not fire$>; the caller drives it viainit().await), exactly as the machine does today; - MUST present the async interface methods directly on the public class (no gated wrapper);
- has no casing→machine construction hop, so constructor/domain params reach the single class directly — the flat form is not subject to the casing param-forwarding path of issue #167.
Operations and persist methods are already ungated pass-throughs under the casing; in the flat form they are simply plain methods on the single class. Their behavior is unchanged.
Interaction with RFC-0043 validators
E703is a property of the casing gate. Undercasing: falsethere is no gate, soE703cannot be raised — the author has explicitly taken responsibility for single-driver discipline. This is the point of the opt-out and MUST be documented at the flat-form site.E720(async member requires@@[async]) is unchanged: the flat form is still@@[async], so the requirement holds.E721(sync system composes an async system) is unchanged: composing a flat async system from a sync one is the same soundness hole regardless of casing.
New validator diagnostic — E724
The casing: argument MUST be validated:
- an unknown argument key on
@@[async(...)]→E724; - a non-boolean value for
casing:→E724; casing:on anything other than an@@[async]system →E724.
E724 is a hard compile error (the async family — E703/E720/E721/E722/
E723 — are all hard). The message MUST name the offending key/value and
state the accepted form (@@[async] or @@[async(casing: true|false)]).
Examples
A self-driving kernel that is its own sole driver — no gate wanted:
@@[target("python_3")]
@@[main]
@@[async(casing: false)]
@@system Kernel (host: Host) {
interface:
async tick()
machine:
$Running {
tick() { @@:self.host.step() }
}
domain:
host: Host = host
}
emits a single Kernel class (no _KernelMachine, no busy flag), constructed
directly with host, driven via await k.init() then await k.tick().
The default stays gated:
@@[async]
@@system Counter { interface: async tick() ... }
// → public Counter casing (E703 gate) + private _CounterMachine, as RFC-0043.
Alternatives considered
@@[async_casing]as an opt-in — rejected. It flips the default for every existing@@[async]system (silent loss ofE703), makes the unsafe form the default (opt-in to safety is backwards), leaks the internal “casing” term as surface syntax, and needs its own validation for the@@[async_casing]-without-@@[async]combinations.- A separate sibling attribute (
@@[async] @@[no_gate]/@@[unguarded]) — rejected. Two attributes for one concern; they can drift out of sync, and the pairing is not enforced by the grammar. - Positional (
@@[async(flat)]/@@[async(ungated)]) — viable and matches today’s positional-only attribute args, but the keyed form reads better as the attribute grows options and self-documents the default (casing: true). Chosen: keyedcasing: false(issue #168).
Migration
None. @@[async] behavior is unchanged; the flat form is strictly additive and
opt-in per system. No existing system’s output changes.
Testing (sketch)
- Codegen: a
casing: falsesystem emits one class with the async core and no busy/in-flight/E703/_<Name>Machine;@@[async]and@@[async(casing: true)]are byte-identical to each other and to RFC-0043. - Negative: unknown key, non-bool value, and
casing:on a non-async system each raiseE724. - Behavioral (per async backend): a flat async system constructs directly with
params,
init()/tick()run, and re-entrant external calls are not guarded (noE703) — confirming the opt-out.
Cross-references
- RFC-0043 — the base async layered casing/machine.
- Issue #168 — the design decision and parser investigation this RFC formalizes.
- Issue #167 — casing param forwarding (the path the flat form sidesteps).