fix(core): keep ready across a snapshot

buildSnapshotListing mapped loaded ? kLoaded : kUnloaded and never looked at
`published`, so every snapshot reported an already-published module as merely
`loaded`. Snapshot records draw fresh seqs from the same counter, so they
outrank every earlier transition and win the replay rule -- and the readiness
watch is one-shot, so nothing re-emits. A module downgraded this way stays
`loaded` for the rest of the session while answering calls normally.

Verified against a live daemon by forcing a second snapshot (reload
modules_state after test_fullapi_cpp is ready):

  before this fix:  ready -> snapshot -> loaded
  after:            ready -> snapshot -> ready

`published` is null when no watch is armed, hence the is_boolean check rather
than value(..., false): unknown readiness must report `loaded`, never `ready`.

Not covered by a unit test: buildSnapshotListing is file-local and the 194
existing tests never reach it -- they install no sink, so nothing arms a watch.
The check above is a live-daemon A/B.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-26 22:17:45 -03:00
committed by Dario Lipicar
co-authored by Claude Opus 5
parent 84564f0f6a
commit 2ac002b410
+13 -2
View File
@@ -356,11 +356,22 @@ namespace {
continue;
const bool loaded = info.value("loaded", false);
// Readiness must survive the snapshot. Snapshot records draw fresh
// seqs, so they outrank every earlier transition -- reporting a
// published module as merely `loaded` would clobber its `ready`
// permanently, since the watch is one-shot and will not re-fire.
// `published` is null when no watch is armed, hence the is_boolean
// check: unknown readiness reports `loaded`, never `ready`.
bool published = false;
if (auto p = info.find("published");
p != info.end() && p->is_boolean())
published = p->get<bool>();
nlohmann::json rec = nlohmann::json::object();
rec["module"] = name;
rec["state"] = loaded ? logos::module_state::kLoaded
: logos::module_state::kUnloaded;
rec["state"] = !loaded ? logos::module_state::kUnloaded
: published ? logos::module_state::kReady
: logos::module_state::kLoaded;
rec["path"] = info.value("path", std::string());
rec["type"] = std::string();
rec["version"] = std::string();