Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ Switching backend = a different cfg + different `src/net/<backend>/*.o`.
SPEC §13, issue #70). **§13 was retired at contract v1.0.0; every §13.x
number in this section resolves at tag `v0.17.1`, nowhere else.** No §13
assert has a contract-derived counterparty, so no contract release can
break a build — but the error codes below are asserted NOWHERE, and that
is the live hazard (see the allocation note). `src/net_abi.inc` is the
normative source now. `boot.s`, `http.s`, `tls_record_io.s` and `tls13.s`
break a build. The error codes below used to be asserted NOWHERE; they
are asserted now (#184) — see the allocation note. `src/net_abi.inc` is
the normative source now. `boot.s`, `http.s`, `tls_record_io.s` and `tls13.s`
`.include` it and import no `net_*` symbol directly, so a backend that
drops a symbol fails the link by name on both backends. Surface:

Expand Down Expand Up @@ -304,6 +304,43 @@ drops a symbol fails the link by name on both backends. Surface:
new code in `c64-wireguard/src/net_abi.inc`, which declares itself
canonical for both ranges, then here. It owns `$8C-$8F` and `$46-$49`,
which our two error headers used to present as free (#184).
**That is mechanically enforced now, in two halves (#184).**
`src/net_err_registry_asserts.s` is `$(wildcard src/*.s)`, so it
assembles into every build on both backends and emits **no bytes**: it
holds the peer's codes as `NET_ERR_PEER_*` equates and `.assert`s (scope
`error`, so ca65 settles it before ld65) that no code of ours lands on
one, that each is in family range, and that no published value has been
reassigned. **Two of our own names on one byte also fails the build**:
`NET_ERR_CLAIM_VALUE` defines `.ident(.sprintf("NET_ERR_TAKEN_%02X",
val))`, so a second claim on a byte is a ca65 redefinition error naming
the BYTE (`Symbol 'NET_ERR_TAKEN_88' is already defined`) — O(n), no
list to maintain, still zero bytes. It covers only codes passed through
the macros. Its blind spot — a code never registered — is closed by
`tools/test_net_err_registry.py`, which parses the two headers instead
and diffs our snapshot's **values and names** against the live peer file
(`C64_WIREGUARD_ROOT`, else `../c64-wireguard`, else
`~/Documents/c64-wireguard`). A missing checkout is an **involuntary
skip**, so those four checks FAIL rather than pass quietly
(`tools/_skip_policy.py`, #178) — a fresh clone with no peer checkout is
RED by design; `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 check). **Scope the guarantee
in both directions.** Under-coverage is **three** things, and a code
reaches a blind spot only by being invisible to *both* halves: (1) an
expression-valued code that is ALSO never registered — the **suite**
reads only `NAME = $hh`, `NAME = ddd` and `.define NAME $hh`, while ca65
evaluates an expression fine once registered, so neither alone is the
gap; (2) a code named without the `_ERR_` infix that is ALSO never
registered — a gap the over-coverage gate below CREATED rather than
inherited, and the accepted cost of it; (3) a bare inline `lda #$8C`
with no equate at all, which no text guard sees (latent — no such site
exists). Write literals, name them `*_ERR_*`, register them.
Over-coverage: the suite reads whole headers that also hold ordinary
constants, so it gates on that same `_ERR_` infix; without it a future
`UCI_HOST_BUF_MAX = 64` would be reported as an ip65-family
error code needing allocation in c64-wireguard's registry. The
`NET_FAMILY_*` bits in `src/net/net_families.inc` are the same cross-repo
copy problem and are still unguarded.
- Gone, per §13.1: `net_tcp_set_recv_cb` (stub), `net_recv_ready`,
`net_dhcp` (alias), and `net_print_ip` — IP printing is consumer UI and
is now `print_local_ip` in `boot.s`, one copy for both backends.
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ testpaths =
tools/test_device_lock_timeout.py
tools/test_flags_stamp_skip_is_loud.py
tools/test_ip65_hw_checks_unit.py
tools/test_net_err_registry.py
tools/test_net_test_env.py
tools/test_package_verify.py
tools/test_pins.py
Expand Down
40 changes: 40 additions & 0 deletions src/net/ip65/ip65_errors.inc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,46 @@
; collision, caught in review three weeks after the $88 one. Remapped before
; merge. Comments only, deliberately: an equate here would imply we emit
; them. Their registry is the authority, not this snapshot (#184).
;
; THAT SNAPSHOT IS NOW MACHINE-CHECKED (#184). The list above is prose, and
; prose does not fail a build, so two guards stand behind it:
;
; src/net_err_registry_asserts.s assembles into EVERY build, both
; backends, every profile, and emits no bytes. It `.assert`s that no
; code defined here lands on a peer-owned value, that each is in the
; $40-$7F family range, and that no published value has been
; reassigned; NET_ERR_CLAIM_VALUE additionally makes two of our own
; names on one byte a ca65 redefinition error. Minting $46 fails the
; build with a named message instead of linking clean.
; tools/test_net_err_registry.py parses THIS file rather than the
; asserts TU, so a new code that never gets registered there is still
; caught, and diffs our snapshot's values AND names against the live
; c64-wireguard/src/net_abi.inc.
;
; Adding a code: allocate in c64-wireguard/src/net_abi.inc, add the equate
; here, then register it in src/net_err_registry_asserts.s.
;
; WHAT THAT PAIR DOES NOT COVER — the guarantee is real but narrow:
; - **Write the value as a literal.** The suite recognises `NAME = $hh`,
; `NAME = ddd` and `.define NAME $hh` and nothing else. An expression —
; say `NET_ERR_IP65_NEW = NET_ERR_IP65_SEND + 1` — is invisible to it.
; ca65 evaluates such a code fine ONCE REGISTERED, so the gap is the
; narrow one: an expression-valued code that is also never registered
; in src/net_err_registry_asserts.s escapes both halves. Every code
; below is a literal; keep it that way.
; - An inline immediate (`lda #$46 / sta net_last_error`) with no equate
; behind it is invisible to both. No such site exists today.
; - The cross-repo half needs a c64-wireguard checkout. Without one those
; checks FAIL — an involuntary skip is a failure — rather than passing
; quietly; `C64_NO_PEER_REGISTRY=1` is the loud opt-out.
; - It can also go red the OTHER way: the suite reads this whole file, so
; an ordinary constant that happens to land in $40-$7F would look like
; an allocation. It is kept out by the `_ERR_` infix every code here
; uses — so name a new code `NET_ERR_IP65_*`, and expect no help from
; the suite if you do not.
; - Nothing here re-verifies c64-wireguard. A collision found against
; their registry is a cross-repo finding for a human, not something
; either repo may renumber unilaterally.

.ifndef IP65_ERRORS_INC_INCLUDED
IP65_ERRORS_INC_INCLUDED = 1
Expand Down
41 changes: 41 additions & 0 deletions src/net/uci/uci_errors.inc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,47 @@
; them. Re-read their registry before allocating; this list is a snapshot,
; not the authority (#184).
;
; THAT SNAPSHOT IS NOW MACHINE-CHECKED (#184). The list above is prose, and
; prose does not fail a build, so two guards stand behind it:
;
; src/net_err_registry_asserts.s assembles into EVERY build, both
; backends, every profile, and emits no bytes. It carries the peer's
; codes as NET_ERR_PEER_* equates and `.assert`s that no code defined
; here lands on one, that each is in family range, and that no
; published value has been reassigned; NET_ERR_CLAIM_VALUE
; additionally makes two of our own names on one byte a ca65
; redefinition error. Minting $8C fails the build with a named
; message instead of linking clean.
; tools/test_net_err_registry.py parses THIS file rather than the
; asserts TU, so a new code that never gets registered there is still
; caught, and diffs our snapshot's values AND names against the live
; c64-wireguard/src/net_abi.inc.
;
; Adding a code: allocate in c64-wireguard/src/net_abi.inc, add the equate
; here, then register it in src/net_err_registry_asserts.s.
;
; WHAT THAT PAIR DOES NOT COVER — the guarantee is real but narrow:
; - **Write the value as a literal.** The suite recognises `NAME = $hh`,
; `NAME = ddd` and `.define NAME $hh` and nothing else. An expression —
; say `UCI_ERR_NEW = UCI_ERR_NO_SOCKET + 4` — is invisible to it.
; ca65 evaluates such a code fine ONCE REGISTERED, so the gap is the
; narrow one: an expression-valued code that is also never registered
; in src/net_err_registry_asserts.s escapes both halves. Every code
; below is a literal; keep it that way.
; - An inline immediate (`lda #$8C / sta net_last_error`) with no equate
; behind it is invisible to both. No such site exists today.
; - The cross-repo half needs a c64-wireguard checkout. Without one those
; checks FAIL — an involuntary skip is a failure — rather than passing
; quietly; `C64_NO_PEER_REGISTRY=1` is the loud opt-out.
; - It can also go red the OTHER way: the suite reads this whole file, so
; an ordinary constant that happens to land in $80-$BF would look like
; an allocation. It is kept out by the `_ERR_` infix every code here
; uses — so name a new code `UCI_ERR_*`, and expect no help from the
; suite if you do not.
; - Nothing here re-verifies c64-wireguard. A collision found against
; their registry is a cross-repo finding for a human, not something
; either repo may renumber unilaterally.
;
; One inconsistency, called out rather than hidden: $8A below IS a real
; equate, and is likewise reserved-never-emitted. It predates this block and
; stays — a published value is never reassigned, and removing the equate
Expand Down
Loading