docs: the walkthrough raced the handshake and silently watched nothing

Running the README walkthrough end-to-end for the first time turned up two
races, both of which pass silently rather than failing loudly:

  * `-D &` returns before the daemon is listening, so the `load-module` on
    the next line races the socket. Poll `status` instead.

  * `watch` is the one-shot subscription this README already warns about:
    refused before the registry handshake, and never retried. Subscribing
    ~2s after load-module answers WATCH_FAILED, and because nothing retries,
    every later line still succeeds while observing NOTHING. Measured on
    macOS: 2s fails, 6s succeeds.

The second is the dangerous one — the walkthrough "passed" while its only
event assertion was dead. Neither is a module bug; the generated typed
wrapper uses onEventWhenAvailable and needs none of this.

Also record the verified answers, since the contract claims are now measured
rather than reasoned: module_record miss -> {"status":"ok","result":null},
a replayed seq -> "result":false, list_modules -> partial:true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-26 13:50:32 -03:00
co-authored by Claude Opus 5
parent 3ba7861978
commit be88d0d32d
+26
View File
@@ -151,8 +151,28 @@ lm result/lib/modules_state_plugin.dylib
LSDIR=$(mktemp -d) # per-invocation: a leaked daemon poisons later runs
export LOGOS_MODULES_STATE_TEST_INGEST=1
logoscore --config-dir "$LSDIR" -D -m "$PWD/result-install/modules" &
# WAIT for the daemon to accept commands. `-D &` returns before it is
# listening, so a load-module issued straight after it races the socket.
until logoscore --config-dir "$LSDIR" status >/dev/null 2>&1; do sleep 0.5; done
logoscore --config-dir "$LSDIR" load-module modules_state
# THEN WAIT AGAIN before subscribing, and do not shorten this.
#
# `watch` is the hand-rolled subscription this README warns about above: it is
# ONE-SHOT and is REFUSED before the registry handshake completes. Subscribing
# ~2s after load-module reliably answers
# {"code":"WATCH_FAILED","message":"Failed to watch events ..."}
# and — because it is one-shot — it never retries, so the rest of the script
# runs and passes while silently observing nothing. Measured on macOS: 2s
# fails, 6s succeeds. This is a race, not a module bug; the generated typed
# wrapper (`logos.modules_state.on(...)`) uses onEventWhenAvailable and needs
# none of this.
sleep 6
logoscore --config-dir "$LSDIR" watch modules_state --event module_state_changed &
sleep 2
logoscore --config-dir "$LSDIR" call modules_state note_transition \
dev chat_module 'json:null' 'json:null' unloaded loaded 'json:null' 1
logoscore --config-dir "$LSDIR" call modules_state list_modules
@@ -161,6 +181,12 @@ logoscore --config-dir "$LSDIR" stop
An empty optional in a positional slot is `'json:null'` — arity never changes.
A miss comes back as `{"status":"ok","result":null}``null` is the empty
optional, **not** a failed call. Verified end-to-end:
`module_record nope` answers exactly that, while `list_modules` answers
`{"modules":[...],"partial":true,"seq":2}` and a replayed `seq` answers
`"result":false`.
## Thread safety
Handlers are serialised today by `"concurrency": "single"`, but the registry