Skip to content

Close #184 — mechanically enforce the fleet net_last_error registry - #222

Merged
JC-000 merged 4 commits into
masterfrom
fix/184-error-code-registry
Sep 8, 2026
Merged

Close #184 — mechanically enforce the fleet net_last_error registry#222
JC-000 merged 4 commits into
masterfrom
fix/184-error-code-registry

Conversation

@JC-000

@JC-000 JC-000 commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes #184.

#185 wrote c64-wireguard's $8C-$8F / $46-$49 allocations into our two headers as prose and closed the "presents them as free" half. The half left open is the one the issue ends on: whether a snapshot plus a pointer is enough. It isn't — this namespace has collided twice ($88, live four days; wg#120's first commit over our $41-$45) and prose caught one of the two.

Two halves, each covering the other's blind spot

src/net_err_registry_asserts.s — picked up by $(wildcard src/*.s), so it assembles into every build on both backends and emits no bytes (present in all seven maps checked, claiming zero segments in every one). It holds the peer's codes as NET_ERR_PEER_* equates and asserts that no code of ours lands on one, that each is in family range, and that no published value is reassigned. NET_ERR_CLAIM_VALUE defines .ident(.sprintf("NET_ERR_TAKEN_%02X", val)), so two of our own names on one byte is a ca65 redefinition error naming the byte — O(n), no list to maintain. Scope error, so ca65 settles everything at the offending object rather than at the link.

tools/test_net_err_registry.py — 10 checks, pure logic, milliseconds. Parses the headers, so it catches codes that never reached the asserts TU (which is what makes the assembler half's coverage safe), and diffs our snapshot's values and names against the live peer registry.

Scope of the guarantee, stated in all four documentation sites

Under-coverage. The suite reads NAME = $hh, NAME = ddd, .define NAME $hh and nothing else. Three things are invisible to both halves: an expression-valued code that is also never registered; a code not named with _ERR_ that is also never registered (this class is created by the name gate below — measured, 3 failures before it, 0 after); and a bare inline lda #$8C with no equate (latent — no such site exists). Note the qualifier: neither the expression limit nor the registration limit is a gap on its own, since ca65 evaluates a registered expression-valued code fine.

Over-coverage. The suite reads whole headers that also hold ordinary constants, so it gates on the _ERR_ infix. Without it a future UCI_HOST_BUF_MAX = 64 would be reported as an ip65-family error code needing allocation in a peer repo's registry — and buffer sizes favour 64 and 128, i.e. $40 and $80, the first byte of each family. The gate's cost is bounded because the assemble-time half is name-blind and catches such a code the moment it is registered.

A fresh clone with no c64-wireguard checkout is RED by design

A missing peer checkout is an involuntary skip under tools/_skip_policy.py (#158/#165/#178): the four cross-repo checks fail rather than pass quietly, because the check is applicable and unverified and exit 0 would say otherwise. C64_NO_PEER_REGISTRY=1 is the loud opt-out — its own variable, deliberately not C64_ALLOW_SKIP, which also gates test_build_flags_stamp.py's toolchain prerequisite and would otherwise be silenced by anyone quieting this suite.

Red-green

Sixteen injected cases across three review rounds. ca65 red on a peer collision inside a real make, on a reassigned published value, and on an intra-repo duplicate under both backends (no PRG produced). The suite red on an unregistered code, an unregistered duplicate, the decimal and .define spellings, an unasserted snapshot row, and a simulated peer rename. Plus three expected-GREEN cases capturing the documented limits, so they are demonstrated rather than claimed — the reviewer confirmed the expression case emits no diagnostic at all rather than passing incidentally.

The case worth reading twice: an unregistered $8C passes ca65 and is caught by the suite; an intra-repo duplicate passes the suite's peer checks and is caught by ca65. That mutual coverage is why both halves exist rather than one.

Cost

PRG sha256 byte-identical to master on all five profiles, re-verified after each of three review rounds:

17fabac24c4dd286e3d7fd4265640bb4bd7e67995c667cbc1fd8c74461ba5b9c  ip65-default
52ff9c2d8b41d23ea5b151eefe15242807b75d0d930594d59575bedc9afd1126  ip65-wikipedia
9eaee61d0fe4e40d2dfe2311abdefd0988718ee4692620d8980adfa14fdff960  uci-default
971b074f65c77eb53b84c9f8f10074bbff81105953e6a04c69533289937977dc  uci-onchip
00e4370ce3086e8b30df243861e56725a09797f08d8e668535c5166c62882152  uci-comb

Independently rebuilt by the reviewer across seven configurations, including the 14 B-margin wikipedia target and the armed ENABLE_P384_VERIFY=1 link. .ident/.sprintf collateral probed: out-of-family values report the range error rather than a NET_ERR_TAKEN artefact, $1234 and 0-1 assemble cleanly, and the namespace appears nowhere outside the TU.

Root pytest: 163 passed.

Cross-repo

No collision at c64-wireguard@cf7b41e; all 16 of our codes appear in their registry with the ownership we claim.

Review

Three adversarial rounds. Round 1 blocked on a crash (pytest.skip() raises a BaseException the standalone main() did not catch, killing the suite mid-run and reporting "failed" when it meant "not verified") and on prose claiming a completeness the code lacked. Round 2 withdrew that block and found the guard covered exactly one spelling of an equate. Round 3 cleared it.

Three of three rounds caught a false mechanism claim in this branch's prose — "the assembler cannot express this" (falsified by a four-line .ident experiment, which then became the strongest guard here), "expressions pass both halves while colliding" (too pessimistic), and "the pre-existing limit this does not widen" (the gate created the class). All three were scope claims written in the same breath as a tested result and not given the same scrutiny; all three survived a green suite and an unmoved hash; each was falsifiable in about a minute. Recorded because it is the failure mode this repo keeps paying for.

Follow-ups, not in this PR

  • Vendor the peer table (or its hash) in-tree and run the structural half against the vendored copy unconditionally, leaving only live-drift comparison needing a checkout — the same shape as libs/nistcurves' in-tree pin plus check_upstream_pins.py. That would make a fresh clone green and honest at once.
  • src/net/net_families.inc has the identical unchecked cross-repo copy problem; noted in CLAUDE.md, needs the wireguard lane.

🤖 Generated with Claude Code

JC-000 and others added 4 commits September 7, 2026 23:02
The ip65 ($40-$7F) and UCI ($80-$BF) error families are one namespace each,
shared with c64-wireguard. #185 wrote their allocations into our two headers
as prose. Prose does not fail a build, and the fleet has already had two
collisions -- $88 (live four days) and wg#120's first commit minting $40-$44
over our $41-$45, caught only by a human reviewer.

Two guards, one for each other's blind spot:

  src/net_err_registry_asserts.s -- picked up by the Makefile's
  $(wildcard src/*.s), so it assembles into every build, both backends,
  every profile, and emits no bytes (equates and .assert only; PRG sha256
  unchanged on all five profiles). It carries the peer's codes as
  NET_ERR_PEER_* equates -- a namespace that is obviously not ours, so the
  #185 rule that an equate in our headers would read as "we emit this"
  still holds -- and asserts that no code of ours lands on one, that each
  is in family range, and that no published value has been reassigned.
  Scope `error`, not `lderror`: every operand is a local equate, so ca65
  settles it at the offending object rather than at the link.

  tools/test_net_err_registry.py -- pure logic, milliseconds, no build.
  Parses the two headers rather than the asserts TU, so a code that never
  gets registered there is still caught, and diffs our snapshot against the
  live c64-wireguard/src/net_abi.inc when a checkout is present. Absent
  one it skips with a reason; pytest.ini already sets addopts = -ra, so
  that reason prints on every run.

$8A UCI_ERR_LONG_READ is the one deliberate overlap -- their allocation,
mirrored here reserved-never-emitted -- so it is asserted to EQUAL their
value rather than differ from it, and is excluded from the collision sweep
by name, never by value.

Cross-repo state at c64-wireguard cf7b41e: no collision. All 16 codes we
define are listed in their registry with the ownership we claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…view)

Adversary review of the first commit. The zero-byte / every-profile /
cross-product engineering held; these are the guard's advertised scope and
one crash.

F3 -- the standalone entry point died mid-suite. _require_peer() called
pytest.skip(), which raises Skipped, a BaseException; main() caught only
AssertionError. Five of eight checks never ran, no summary printed, and the
exit 1 meant "a check FAILED" under this repo's contract. That is verbatim
the failure _skip_policy.VoluntarySkip exists to prevent, and the
`if root is None` fallbacks were dead code whenever pytest was importable.

F4 -- and it re-decided the skip policy locally instead of using
tools/_skip_policy.py, so it carried no coverage clause and no opt_out_env.
Now require(..., executed=6, total=10, certifies=..., opt_out_env=
"C64_ALLOW_SKIP"): a missing peer checkout is an INVOLUNTARY skip, so the
four cross-repo checks FAIL rather than pass quietly. C64_ALLOW_SKIP=1 is
the loud opt-out. The six structural checks run either way.

F2 -- nothing checked our own set for pairwise distinctness. UCI_ERR_DUP =
$88, duly registered, passed all eight checks including the peer one,
because $88 IS in their registry under another name. The assembler cannot
express this (its literal pins only cover codes that existed when they were
written), so it is a new suite check.

F1 -- the parser knew one spelling. Widened to `NAME = ddd` and
`.define NAME $hh`; verified no existing decimal equate in either header
falls in $40-$BF. Expression-valued equates are DECLARED OUT OF SCOPE in
all four documentation sites: evaluating them needs an assembler, and a
parser that silently mis-evaluates one would be worse than one that
visibly does not try.

F5 -- the $8A mirror compared values only, so a peer RENAME left every
check green while our diagnostics carried a dead name. New check compares
names too, for the whole snapshot; the commit message's "cannot silently
drift off theirs" was true of the value and false of the name, and the
TU comment now says which.

F6 -- the macros' peer list is hand-written and ca65 cannot iterate a
table, so a snapshot row with no matching .assert was silently unchecked.
Now checked by the suite, and noted where someone editing the table sees it.

F7 -- "adding a code takes all three edits and the suite is red until they
agree" claimed a completeness F1 disproves. Corrected in both headers, the
TU header and CLAUDE.md, each of which now carries what the pair does NOT
cover.

PRG sha256 unchanged on all five profiles; no assertion logic changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tions (#184 review 2)

N1 -- "the assembler cannot express this" was a FALSE MECHANISM CLAIM, and
it foreclosed the better guard for a round. ca65 expresses it in one line:

    .macro NET_ERR_CLAIM_VALUE val
        .ident(.sprintf("NET_ERR_TAKEN_%02X", val)) = 1
    .endmacro

A second claim on a byte is a redefinition error naming the BYTE. O(n), no
hand-maintained list, constant equate so still zero bytes. Called from both
assert macros and once for the $8A mirror (which cannot go through a macro
that asserts the value differs from every peer code). An intra-repo
duplicate now fails the build on all five profiles instead of being caught
only by a suite `make` never runs. The claim was wrong in four places and is
corrected in all four; the narrower TRUE sentence it was over-generalised
from -- the literal pins give distinctness only among codes that existed
when they were written -- is kept.

While verifying that, a SECOND over-pessimistic claim fell: ca65 evaluates
whatever the macro is handed, so a REGISTERED expression-valued equate fires
the collision assert exactly like a literal (measured). The residual gap is
only an expression-valued code that is ALSO never registered. Corrected in
the same four sites; the earlier "passes both halves while colliding" was
wrong.

N2 -- the decimal widening introduced a false-positive class: an innocent
`UCI_HOST_BUF_MAX = 64` produced three red checks telling the author to
allocate a buffer size in c64-wireguard's error registry. Gated on the
`_ERR_` infix both headers use universally for codes and never for
constants. DECISION and its cost: this lets a real code named without
`_ERR_` (`UCI_STATUS_FOO = $8C`) past the suite. Accepted because the
assemble-time half is name-blind and still catches it the moment it is
registered, and because the false positive is a when rather than an if --
buffer sizes favour 64 and 128, which are $40 and $80, the first byte of
each family. Named in every scope block.

N3 -- moved the opt-out off the shared C64_ALLOW_SKIP (which also gates
test_build_flags_stamp.py's toolchain prerequisite) to a dedicated
C64_NO_PEER_REGISTRY. One hatch, one door.

N4 -- the two headers' scope blocks are parallel again; ip65's kept a
round-1 sentence that understated the missing-checkout behaviour.

N5 -- inline immediates (`lda #$8C / sta net_last_error`) named as invisible
to both halves. Latent: no such site exists.

Every scope block now states over-coverage as well as under-coverage.
Also: _our_codes()'s bare `assert` became a named RegistryParseError (it
vanished under python -O and misattributed to whichever test ran first),
and _peer_snapshot_expected_name() gained PEER_NAME_OVERRIDES for a future
peer name fitting neither derivation.

Keeping cannot_run per the coordinator: the check is applicable and
unverified, and exit 0 would say otherwise.

PRG sha256 unchanged on all five profiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ims (#184 review 3)

Text only -- comments, docstrings and CLAUDE.md prose. No executable line
in either guard changed; verified by filtering the .s and .py diffs, and by
rebuilding all five profiles to unmoved PRG hashes.

N6 -- the fourth untested mechanism claim in this area, and the one that
mattered. The ERR_NAME_MARKER comment said a non-`_ERR_`-named unregistered
code "is invisible either way, which is the pre-existing limit this does not
widen". Measured on the two commits either side of the gate, with the same
mutation (an unregistered, colliding UCI_STATUS_FOO = $8C): 3 failures at
53a35bb, 0 at 123fccb. The gate CREATED that class; an `_ERR_`-named
unregistered code is caught at both revisions, so nothing was being
restated. Corrected to say so. The decision two lines above is unchanged
and still right -- only the blast radius was wrong.

N7 -- CLAUDE.md listed under-coverage as two members while both headers and
the suite docstring named three, and CLAUDE.md is what a future reader
consults. It now enumerates all three, each with the "invisible to BOTH
halves" qualifier that is what actually makes something a blind spot, and
names (2) as a gap the gate created rather than inherited.

N8 -- RegistryParseError's docstring claimed to have fixed the
misattribution as well as the `python -O` erasure. Only the erasure and the
naming are fixed: the exception is still raised from a helper and surfaces
under every test that calls _our_codes(). Clause dropped, limitation
stated instead.

N9 -- kept NET_ERR_CLAIM_VALUE UCI_ERR_LONG_READ and recorded why, so the
next reader does not re-derive that it changes no outcome: it does not, and
was checked. It stays to keep the invariant total -- every code this repo
defines claims its byte, no exception to carry in your head -- and to give
the next macro-exempt code an obvious home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JC-000
JC-000 merged commit 3c5a5f4 into master Sep 8, 2026
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.

Mirror c64-wireguard's net_last_error registry: our headers present $8C-$8F and $46-$49 as free

1 participant