Skip to content

Controls batch 13b: stored-automation start (#1635-#1639), core-owned observation addressing and limits (#1676-#1678) - #1687

Merged
tamashi095 merged 30 commits into
mainfrom
codex/controls-batch-13b
Oct 15, 2026
Merged

tamashi095 merged 30 commits into
mainfrom
codex/controls-batch-13b

Conversation

@tamashi095

@tamashi095 tamashi095 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Controls batch 13b: stored automation gets its compiler, the fader stage gets timed operations, and every plan holds the session's jump lengths. The protocol retires the stored transport position and the unused span kinds. Observation taps are addressed by stable key and capped in the core, and an effect-row edit whose effect changed is refused in the core, on both hosts.

Closes #1635
Closes #1636
Closes #1637
Closes #1638
Closes #1639
Closes #1676
Closes #1677
Closes #1678

What changed

  • Retire the protocol's stored transport position #1635 TRANSPORT_SET field 2, the snapshot's field 2 and the TRANSPORT_STATE event's field 3 (the stored transport position) are retired. A retired field is refused at decode in both flag forms (UNKNOWN_REQUIRED_FIELD mandatory, MALFORMED_FRAME optional), before any change and before any event is reserved. retired_code_rows() gains the two TRANSPORT_SET rows.
  • Retire the automation span kinds and the sample rate that no producer uses #1636 AutomationSpanKind is {Point} and AutomationRate is {Block, None}. The retired values are refused and never reallocated. automation_segment_value and the Sample rate are deleted. The protocol half was already gone, so nothing on the wire moved.
  • Compile stored automation into per-cell events in node time #1637 New crate crates/automation (no_std + alloc, depends on math only). It compiles stored automation into per-cell programs (ProgramBuilder, CellProgram) and generates events in node time on render (CellState, NodeSpan, CellEvents), allocation-free. The G5 scalar corpus is appended to the wasm gate corpus.
  • Apply timed operations inside a block on the strip fader stage #1638 The fader stage takes timed operations inside a block: FaderOperation, a fixed-size FaderOperationSink on the render stack, and the pull trait FaderOperationSource. process_with cuts the block at each offset, so a split moves no bit. A lane's gain is held during a mute or unmute ramp (mute_ramp, carried across a swap). The live F22 rule: set_fader_gain on a muted lane only remembers the gain. New counter fader_operations_stopped. Nothing feeds timed operations to the fader stage until Render moving stored fader automation, seeks and latency #1647: no caller of process_with or try_process_settled_with_matrix_and_operations exists outside crates/builtins (checked on the integrated tree). Root ruled that Apply timed operations inside a block on the strip fader stage #1638 closes on its rescoped record: the unmute-ramp reissue mechanism is Render moving stored fader automation, seeks and latency #1647's design (its batch 13b amendment: requirement, constraints 1-7, gate 8 cases (a)-(e)), and Classify fader automation edits as carried rebuilds #1648 carries it.
  • Hold the automation jump lengths in a plan cell #1639 Every graph plan holds one latest-target cell of the session's fader, mute and pan ramp lengths (GraphJumpLengths). Render reads it once per block at block entry, after the live snapshot. A control_smoothing change on a session whose automation reads a length is a live edit that writes the cell under the transaction's stamp, after every fallible check and before publication. Preparation refuses a handle set without the writer, so every epoch holds one.
  • Cap the observation bindings by what one ended event holds #1676 One core cap on observation bindings: how many keys one OBSERVATION_ENDED frame holds with maximum IDs (1 KiB: 3, 4 KiB: 12, the browser's 16 KiB: 51). A batch is admitted whole by admit_and_push_observation_batch before any push. The browser refuses past the cap with the new command reason 15 observationLimit (RESULT_UNSUPPORTED, never backpressure; reasons 12-14 stay retired), and the SDK does not retry it. A tap named twice in one batch is refused. The ended-event reserve is charged by min(slots, cap) keys, not by the frame size.
  • Address observation records by stable key in the core #1677 A browser observation record names its tap by stable key (strip ID, rack, effect ID, tap ID), resolved in the core by SessionState::resolve_observation_key on the newest committed plan. The record is 272 bytes (was 48); the command staging grows from 12,288 to 69,632 bytes, allocated at boot. The SDK's index remapping is deleted.
  • Refuse an effect-row edit whose effect changed, in the core #1678 SET_CONTROL's address is its own message with an optional field 7, the effect the edit was written for. The core refuses a stale edit with control.effect.changed on both hosts (MisoControlRefusal in the SDK). The SDK's effectRowResolver is deleted. A stored automation target refuses field 7 in every section (review fold, root ruling).

Evidence

Re-pins (each from a measurement)

Review

Per-issue adversarial reviews (opus-high): #1637 PASS at attempt 3; #1638 three record FAILs and then the root rescope; #1676 PASS at attempt 2 with three minors folded; #1639 PASS with five minors folded (a typed writer, the cell's bytes measured, a C ABI test that a successor is seeded from the committed model, red on a constant seed).

One opus-high batch review of origin/main...4bbbcb2: FAIL on docs only. The code held: render stays allocation-, lock- and syscall-free; no ack precedes a drop on any new path (#1635 decode refusal, #1678 prepare refusal, #1676 whole-batch admission before any push, #1639 write after every fallible step); the hosts share the core; no placeholder code. Folded in 94a953f (code) and cddff37 (docs):

Recorded for root, no change here: #1676's cap assumes two 127-byte IDs per key (#1685 below). A SetMute that does not change the mute state still retargets the fader ramp (now a #1651 constraint).

Departures recorded in the specs

Issues to file from this batch's specs

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

tamashi095 and others added 30 commits October 15, 2026 14:00
A browser observation record (kinds 7 and 8) names its tap by stable key
(strip ID, rack, slot or insert ID, tap ID), the key OBSERVATION_ENDED uses;
SessionState::resolve_observation_key resolves it on the newest committed plan
allocation-free and refuses an unknown key with a typed reason before any
write. host-web only decodes the staged bytes; the SDK's index re-addressing,
remap and epoch wait are deleted. The record grows to 272 bytes; the ABI
mirrors follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TRANSPORT_SET field 2, the snapshot's field 2 and the TRANSPORT_STATE event's
field 3 are retired and refused in either flag form; the transport carries its
state and effective sample only. The stored position nothing read leaves the
host-core provider and the mock. COMPLETE_SCHEMA_HASH 0xcc9a7bcc502c4936 ->
0x331654b324d076e8 (#1635 retires the stored transport position); the capi
pcm_digest is unchanged (bf7dcedd1d92621f).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A SET_CONTROL edit may name the native effect it was written for (field 7 of
the new SetControlAddress, inserts and console only); the core refuses a
mismatch with control.effect.changed before any write, on both hosts through
the one SessionStore. The SDK builders send the effect and the client-side
effectRowResolver is deleted. COMPLETE_SCHEMA_HASH re-pinned to
0x63ff2e60976e0e21 (#1678 adds the expected-effect field); the capi pcm_digest
is unchanged (bf7dcedd1d92621f).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

The effect contract keeps only Point spans and the Block and None automation
rates; automation_segment_value and the Sample rate are deleted and the retired
values are refused by the descriptor and span checks, never renumbered. The
conformance mock, the randomized generator, the parameter-metadata rate table
and the docs follow. No wire frame carries the retired values, so the protocol
hash does not move; the capi pcm_digest is unchanged (bf7dcedd1d92621f).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cap is the number of keys one OBSERVATION_ENDED frame carries with
both IDs at their longest (3 at 1 KiB, 12 at 4 KiB, 51 at 16 KiB). A
batch that would leave more taps armed is refused before any ack; the
browser reports it as backpressure at the crossing record. Each ended
event cell reserves min(slots, cap) keys at the high-water slot count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New math-only crate `automation`: evaluates stored segments, builds a
per-cell program with a stored discontinuity list, and emits at most
2*ceil(q/64)+3 events per cell per block, invariant to quantum and seek
position, with no allocation on the event path. Its scalar f64 pins
join the cross-target wasm gate corpus as a width-independent block.
Realtime-policy floors move to the tree's counts (31 files, 143 regions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes: the per-cell event bound is 2*ceil(q/64)+3, plus one for a
no_restart cell (CellProgram::max_events, tight on the review's repro);
the builder refuses discontinuities closer than 64 samples, as the
session does (#1626); a deferral behind a forward span is due at its
start, never a wrapped offset; a jump to the in-flight target still
lands; the corpus test leaves the digests to G5; realtime regions cover
the render-reachable accessors (floors 32 files, 144 regions). Corpus
case 2's table moved a step from 3190 to 3180 to meet the spacing rule,
so its G5 pin moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shipped it

The caller resolves the fader row's jump length for each block with
LiveRamps::for_row and passes one u32 to CellEvents::new; #1637 stores no
Word(Fader) key (root, 2026-10-15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A deferred no_restart jump that comes due takes the same lane check as a
discontinuity: while a ramp the caller reported later is in flight, it
waits for the ramp's last sample plus one (D4). The bound test again
proves its tables stress the bound; the spec's D4 and gate 3 state the
no_restart +1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1676)

Root ruling 2026-10-15: a cap refusal is never BACKPRESSURE. New command
reason 15 observationLimit (result UNSUPPORTED, not retried by the SDK) in
every reason list; the reason gates allow the retired gap 12-14 and
require the values otherwise dense. One core entry point,
admit_and_push_observation_batch, checks the cap, then the queue room,
then pushes; the single push is crate-private. The core test uses
127-byte IDs; a refusal at a record after the first is tested on both
the core and the browser; the SDK names the cap in its error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… seek landing on a busy lane

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ission

The core refuses a second naming of one tap in a batch
(ObservationRefusal::Duplicate; the browser answers MALFORMED as before),
so no caller can arm past the cap by naming a tap twice. Admission counts
per producer in scratch built with the plan's observation slots, O(n) and
allocation-free. MUTATIONS.md rows follow the self-tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FaderRampStage::process_with splits a block at each operation offset
(RetargetGain, SetGain, SetMute, RememberGain) and runs the existing
plane kernel over each piece, bit-equal to split blocks. A per-lane flag
marks a mute ramp toward 0; a gain change while muted or during it only
remembers the gain, for timed operations and, per the amendment, for
the live setter too. No production path calls process_with yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1638)

Review fold, no rendered change: the decision record states the unmute-
ramp hold's successor obligation, and #1647 gains D1.6 and gate 8 (a
remembered Jump or Set is reissued as one RetargetGain at the held
ramp's end). FaderOperationSource states its contract; a block that
stops pulling on a non-rising offset counts fader_operations_stopped.
The fused settled path's decline has its test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s reported, the reissue's own hold, a moving-curve gate

Docs only (attempt 3 of #1638, record fold).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… constraints, gate 8; #1648 carries it

#1638's attempt-3 review found two more gaps in the reissue mechanism,
both successor design. #1638's code passed three reviews; its obligation
is this record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every graph plan holds one latest-target cell of the session's fader, mute
and pan ramp lengths. Render reads it once per block at block entry. A
control_smoothing change on a session whose automation reads a length is
a live edit that writes the cell under the transaction's stamp.

Re-pinned plan byte rows, each "the jump-length cell, #1639 D1".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
COMPLETE_SCHEMA_HASH is pinned once for #1635 and #1678 together:
0x0c71_96f8_6abe_6d57 (each alone gave 0x3316_54b3_24d0_76e8 and
0x63ff_2e60_976e_0e21; base 0xcc9a_7bcc_502c_4936).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(root ruling)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s cell

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…measured, a successor seed test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s review fold and #1648's amendment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The SDK coalescing key carries the expected effect (m1).
- Test claims match their assertions (m2); an export check is deleted (m3).
- The observation admission refuses a change with no slot, terminally,
  before any push (m7); the redundant strip fallback is gone.
- The browser charges its producer-to-instance table (m8).
- The SDK cap error carries observationLimit as a diagnostic code (m9).
- An empty record ID is malformed (m11).
- The scalar fader's gain_held and ramp_remaining return None past lane 1 (m13).
- A stored automation target refuses the expected-effect field (root ruling).
- Stale names and comments after #1635, #1636 and #1678.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #1647 gate 8 gets its acceptance criteria back, with constraint 7; #1638
  points at cases (c) and (e).
- #1648: jump lengths follow a rebuild on rows that render (root wording),
  release cells carry the D1.6 obligation, the CellState seed is authorized
  (also for #1646).
- One integrated COMPLETE_SCHEMA_HASH re-pin is recorded in #1635, #1678 and
  the conformance record.
- The SDK README describes the core's control.effect.changed refusal.
- Stale prose after #1635 and #1636; #1676's attempt-1 text marked
  superseded; #1584 and #1651 amendments; #1637's reposition wording.
- Placeholders TBD32 (exact-byte observation admission) and TBD33 (remove the
  redundant span kind and end).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ated tree

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tamashi095
tamashi095 merged commit 624a595 into main Oct 15, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment