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 the E703 gate.
  • @@[async(casing: false)] — the flat form. Emits the async system as a single public class carrying the async dispatch core (the init() boundary and await plumbing) but with no casing, no E703 gate, and no _<Name>Machine split.

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 E703 gate;
  • MUST keep init() and the deferred $> cascade boundary (construction does not fire $>; the caller drives it via init().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

  • E703 is a property of the casing gate. Under casing: false there is no gate, so E703 cannot 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 of E703), 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: keyed casing: 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: false system 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 raise E724.
  • Behavioral (per async backend): a flat async system constructs directly with params, init()/tick() run, and re-entrant external calls are not guarded (no E703) — 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).