Skip to content

gate: every opcode carries a recorded observer classification (#972) - #1024

Merged
InauguralPhysicist merged 2 commits into
mainfrom
obs-marker-gate-972
Aug 21, 2026
Merged

gate: every opcode carries a recorded observer classification (#972)#1024
InauguralPhysicist merged 2 commits into
mainfrom
obs-marker-gate-972

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Lands the completeness pin for #972's observer-liveness elision — the marker gate only. No elision, no runtime change: the markers are comments and the gate is a test.

Why a marker, not a scan

The elision fails silently and totally when its reader set is incomplete: a program gates its own bookkeeping off, then reads slots nobody updated, and every binding answers equilibrium forever with no crash and nothing to fail on.

That set cannot be derived from the C. Five derivations were tried on #972 and all five gave a confident wrong answer, in both directions:

# derivation result
1 grep the read-side API missed obs_stall_trajectory() — reads s->used/s->dH/s->entropy with no observer_slot_* call anywhere
2 objdump -dr relocations missed the same reader: a struct-field read emits no symbol reference
3 scan for struct-field reads false-positived on three unrelated functions reading a ->n
4 scan case OP_X: handlers matched nothing — the VM dispatches through CASE(NAME) computed-goto macros — and reported a clean empty set
5 same scan repaired to CASE(X) matched everything, including a scope marker and a writer, because the handler terminator did not match

The C is the open level (a read has arbitrarily many spellings); the enum is the closed one. So the gate classifies nothing. It asks the one mechanically answerable question — has every opcode been classified by a human? — and goes red on any that has not. A missing marker is a loud unanswered question at the moment an opcode is added, which is when its author knows the answer and nobody else ever will. Same shape, same reason, as tools/failsoft_classify_check.sh.

What landed

All 94 opcodes read by hand in src/vm.c and recorded in src/vm.h:

READS=15 WRITES=10 DIAG=1 NONE=68

Two verdicts contradict the opcode names, and only reading finds them:

  • OP_OBSERVE_ASSIGN is a no-opip += 2; DISPATCH();. The slot model observes at OP_OBSERVE_NAME_POST, after the SET.
  • The bare OP_INTERROGATE reads no observer state at allwhen/where/why/how on a value operand return constants, because observer state is binding-keyed and a bare value has no binding.

obs:DIAG is a fourth marker, and a deviation from the three the issue named. OP_LOOP_CAP_CHECK is emitted at every plain loop and its only reach into observer state is eigs_observe_safepoint's SIGUSR1 dump. READS would pin bookkeeping on for every loop in every program and delete the entire win (mechanical-gates §9 — an unbounded closure marks every loop a reader); NONE would silently drop the obligation that the dump must say the gate is closed rather than render every binding as equilibrium (§11). DIAG records that waiver as a marker the elision can enumerate rather than as prose nobody re-reads.

The gate

tools/obs_marker_check.sh, suite section [99t]. Seven assertions, count pinned:

  1. the enum parses at all (vacuity — a derivation that silently returns nothing prints a confident PASS over an empty population)
  2. no orphan markers — the reverse direction of membership, which is what a rename leaves behind
  3. every opcode carries a marker
  4. every marker is in the closed vocabulary (obs:READ is a typo, not a decision)
  5. the OP_COUNT exemption still fires — present, last, and unmarked
  6. opcode-count floor (94; append-only by the bytecode ABI rule, so a decrease is a review event)
  7. a floor on the READS set — this is the liveness scan's input, and its collapse would elide bookkeeping language-wide while every other check stayed green

--reads emits the READS set, so the future liveness scan reads the markers rather than keeping a second copy of them.

Validation

Eleven mutations in --selftest, each verified to be witnessed by exactly one fixture by neutering each check in a copy of the gate. That verification earned its keep twice:

  • the "OP_COUNT is not last" branch and the EXPECTED_CHECKS pin both survived being neutered — advertised guards enforcing nothing. Both now have their own fixture (an opcode appended past the sentinel; a gate-level mutation that deletes a whole assertion).
  • the first meta-mutation run was itself invalid: the mutants were copied outside the tree, so cd "$(dirname "$0")/.." left them unable to see any header and every fixture failed identically. An unstartable mutant is not a caught one — the shipped version passes an absolute VM_HEADER for exactly that reason.

Also fixes a matcher this change falsified in a neighbouring gate: vm_operand_width_check.sh's selftest anchored on OP_INTERROGATE,[space]*/*, which the new marker now sits between. It failed loudly ("could not remove the comment") rather than passing vacuously, which is the only reason it was caught.

Gates

  • release 4070/4070, 0 failed
  • ASan+UBSan detect_leaks=1 4059/4059, 0 failed, leak tally 0
  • src/vm.h verified as markers-only: stripping the 94 markers reproduces HEAD byte-for-byte

What this does NOT do

  • It proves a verdict was recorded, not that the verdict is right. Correctness rests on the handlers having been read; that reading is what this PR is.
  • It stops at each opcode's own handler. OP_CALL/OP_IMPORT/OP_DISPATCH (callee scanned on its own way in), OP_JUMP_BACK's JIT OSR entry (the JIT has its own reader, jit_helper_report_slot), and assembled chunks (vm_run_bytecode/sandbox_run, needing a bytecode twin — chunk_arm_temporal is the precedent) are excluded by name, each with the mechanism that covers it instead.

Refs #972does not close it. Still open there: the elision itself, its measured before/after against #915's ceiling, and the three bypass routes above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Aj9b82JBb8WS3b8ExoV5Yt

InauguralPhysicist and others added 2 commits August 21, 2026 04:45
The elision behind #972 — stop emitting observer bookkeeping when a
program provably never reads observer state — fails silently and
totally if its reader set is incomplete. An unlisted reader means a
program gates its own bookkeeping off and then reads slots nobody
updated: every binding answers "equilibrium" forever, no crash, nothing
to fail on. So the reader set must be pinned against the closed opcode
list, and this lands that pin ALONE — no elision, no runtime change.

The classification is not derived, because it cannot be. Five
derivations were tried on #972 and all five produced a confident WRONG
answer, in both directions:

  1. grep the read-side API           -> missed obs_stall_trajectory(),
                                         which reads s->used/s->dH/
                                         s->entropy with no
                                         observer_slot_* call at all
  2. objdump -dr relocations          -> missed the same reader: a
                                         struct-field read emits no
                                         symbol reference
  3. scan for struct-field reads      -> false-positived on three
                                         unrelated functions reading ->n
  4. classify by scanning `case OP_X:` -> matched NOTHING (the VM
                                         dispatches through CASE(NAME)
                                         computed-goto macros) and
                                         reported a clean, empty set
  5. same scan repaired to CASE(X)    -> matched EVERYTHING, including a
                                         scope marker and a writer,
                                         because the handler terminator
                                         did not match and each scan ran
                                         on into later handlers

The C is the OPEN level: a read can be spelled arbitrarily many ways.
The enum is the CLOSED one. So the gate classifies nothing and asks the
one mechanically answerable question — HAS EVERY OPCODE BEEN CLASSIFIED
BY A HUMAN? A missing marker is a loud unanswered question at the moment
an opcode is added, which is exactly when its author knows the answer
and nobody else ever will. Same shape as failsoft_classify_check.sh.

All 94 opcodes were classified by reading their handlers in vm.c:
READS=15 WRITES=10 DIAG=1 NONE=68. Two verdicts contradict the opcode
names, and only reading finds them: OP_OBSERVE_ASSIGN is a NO-OP (the
slot model observes at OP_OBSERVE_NAME_POST, after the SET), and the
bare OP_INTERROGATE reads NO observer state — when/where/why/how on a
value operand return constants, since observer state is binding-keyed.

obs:DIAG is a fourth marker, added because collapsing it either way is
wrong and the choice is a decision rather than a derivation.
OP_LOOP_CAP_CHECK is emitted at every plain loop and its only reach into
observer state is eigs_observe_safepoint's SIGUSR1 dump. READS would pin
bookkeeping on for every loop in every program and delete the entire win
(mechanical-gates 9: an unbounded closure marks every loop a reader);
NONE would silently drop the obligation that the dump must SAY the gate
is closed rather than render every binding as "equilibrium"
(mechanical-gates 11). DIAG records that waiver as a marker the elision
can enumerate, instead of as prose nobody re-reads.

The gate (tools/obs_marker_check.sh, suite [99t]) runs 7 assertions:
enum parses at all; no orphan markers (the reverse direction — a marker
left behind by a rename); every opcode marked; every marker in the
closed vocabulary; the OP_COUNT exemption still fires (present, last,
unmarked); an opcode-count floor; and a floor on the READS set, which is
the liveness scan's input and whose collapse would elide bookkeeping
language-wide while every other check stayed green.

Self-test: eleven mutations, each verified to be witnessed by exactly one
fixture. That verification found two gaps in the first version — the
"OP_COUNT is not last" branch and the EXPECTED_CHECKS pin both survived
being neutered, i.e. were advertised guards enforcing nothing — and both
now have their own fixture (an opcode appended past the sentinel; a
gate-level mutation that deletes an assertion). The first attempt at that
meta-mutation was itself invalid: the mutants were copied outside the
tree, so `cd $(dirname $0)/..` left them unable to see any header and all
of them failed identically. An unstartable mutant is not a caught one.

Also fixes a matcher this change falsified in a neighbouring gate:
vm_operand_width_check.sh's selftest anchored on `OP_INTERROGATE,[space]*
/*`, which the new marker sits between. It failed loudly ("could not
remove the comment") rather than passing vacuously, which is the only
reason it was noticed.

Gates: release 4070/4070; ASan+UBSan detect_leaks=1 4059/4059, leak
tally 0. No behaviour change — the markers are comments and the gate is
a test.

Refs #972 (does NOT close it: the elision, its perf measurement against
#915's ceiling, and the three named bypass routes — assembled chunks,
the JIT's own reader, and the SIGUSR1 dump's "gate is closed" line —
remain open).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj9b82JBb8WS3b8ExoV5Yt
mechanical-gates 63 says a gate whose blindness depends on which awk is
installed is a coin flip with a green badge, and the dangerous reading is
the one that produces a plausible COUNT while every comparison is false —
no floor fires, so it prints OK while measuring nothing.

Shadowing awk on PATH: mawk, nawk (one-true-awk, what macOS ships) and
busybox awk return the identical clean verdict AND the identical
selftest verdict. Recorded in the header, because that verification is
not automatic and nobody who trusts the list will repeat it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj9b82JBb8WS3b8ExoV5Yt
@InauguralPhysicist
InauguralPhysicist merged commit 36922c7 into main Aug 21, 2026
18 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the obs-marker-gate-972 branch August 21, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant