Raise errors from the status registry instead of returning codes

akerr_reserve_status_range() and akerr_register_status_name() returned
private int enumerations, which was the one place in the library where a
failure was not an akerr_ErrorContext *. They now return one like
everything else: NULL on success, and on refusal an error whose status is
a real code in the library's reserved band, so it can be CATCH-ed,
HANDLE-d, PASS-ed, or left to propagate into a stack trace. Both are
marked AKERR_NOIGNORE, so discarding the result warns at compile time.

AKERR_STATUS_RANGE_OK and AKERR_STATUS_NAME_OK are gone; the remaining
seven codes move into the AKERR_* offset span and get registered names.
AKERR_LAST_LIBRARY_STATUS replaces AKERR_BADEXC as the top of that span
in the reserved-band static assert and the exhaustiveness sweep.

The refusal detail that used to go straight to akerr_log_method now
travels in the error message, so a caller that handles the error decides
whether it is reported. The two-argument akerr_name_for_status() set path
is the exception: it returns a name and cannot raise, so it logs and
releases. akerr_init() likewise has no caller to raise into, so failing
to reserve its own band or name its own codes is logged and fatal --
that can only happen on a misconfigured build, and continuing would
degrade every later stack trace to "Unknown Error".

Move the 1.0.0 upgrade notice out of README.md into UPGRADING.md and
rewrite its return-code tables in terms of the statuses now raised.

Tests: ctest 29/29, coverage 97.5% line / 64.5% branch, mutation 77.5%
(was 77.6%; the new survivors are the fatal init path, which needs a
library built with an undersized name table -- TODO item 7).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-07-30 20:58:47 -04:00
parent c2d077ab89
commit aa953df598
13 changed files with 741 additions and 380 deletions

17
TODO.md
View File

@@ -35,8 +35,8 @@ inside a loop" hazard: that comes from exiting via `break`, not from `switch`.
## 3. No registry introspection
There is no way to ask who owns a status, or to enumerate reservations. The
"coordinate ranges at the dependency-stack level" advice in README.md therefore
has no tooling behind it.
"coordinate ranges at the dependency-stack level" advice in UPGRADING.md
therefore has no tooling behind it.
A read-only accessor plus a dump through `akerr_log_method` would let a startup
self-check or a CI job print the whole map for a linked stack. Cheap, additive,
@@ -63,6 +63,19 @@ exists for migration. Once consumers have moved to
`akerr_register_status_name()`, make the set path a no-op or remove it and leave
`akerr_name_for_status()` as pure lookup.
## 7. The library's own startup failure path is untested
`__akerr_name_library_status()` and the band reservation in `akerr_init()` log
and then terminate when the library cannot name its own codes. Nothing exercises
that: it needs a build whose `AKERR_STATUS_NAME_SLOTS` is too small to hold the
library's own entries, and that macro is `PRIVATE` to the library target, so a
test executable cannot set it. Mutation testing reports those lines as surviving
mutants for this reason.
Closing it means a second library target built with a tiny table plus a
`WILL_FAIL` test linked against it — worth doing when the CMake gains a
sanitizer variant (item 1), since that adds the same machinery.
## Unrelated pre-existing issues
- The `AKERR_USE_STDLIB=OFF` build does not compile at all: `bool`, `PATH_MAX`