* fix(pilot): stop silently swallowing fs errors in agent token helpers
The pilot-agent audit found that both filesystem-touching helpers in
the agent process discarded every fs error class:
- readPersistedToken caught all errors and returned null. ENOENT
(normal first boot) and EACCES / EIO / EROFS (a real disk or
volume-permission failure) were indistinguishable; the latter
silently fell back to "no persisted token", and on a node where
the enrollment token had already been consumed the agent would
enter a re-enrollment loop with no log signal pointing at the disk.
- persistToken caught all errors and logged at console.warn. The
operator saw the next-boot loop with the same diagnostic gap.
Replace both with errno-aware handling:
- readPersistedToken calls fs.readFileSync directly (no TOCTOU race
against existsSync), treats ENOENT as silent, and logs every other
errno at ERROR with the path.
- persistToken logs at ERROR (not WARN) with the failing errno and an
explicit "next agent restart will require re-enrollment until the
volume is writable" message. The agent still continues with the
in-memory token so the current session is unaffected.
Both helpers are now exported (marked @internal) so the new test file
can mock fs and assert the error-class-vs-log-level matrix.
12 unit cases in pilot-agent-fs-errors.test.ts cover ENOENT,
EACCES, EIO, EROFS, ENOSPC, missing errno, empty file, and the
happy path. Mock pattern follows backend/src/__tests__/filesystem.test.ts.
* fix(pilot): address code-review on the fs-error branch
Three findings from the review pass:
- Em dash in a test description (Directive 18). Rewritten as
"does not throw, so the in-memory token stays usable for the
current session".
- persistToken still had an existsSync + mkdirSync pair around
the token-write. mkdirSync({ recursive: true }) is idempotent on
existing directories, so the existsSync was redundant and added
a TOCTOU window where the directory could be removed between the
probe and the write. Dropped the existsSync; the test that
previously primed mockExistsSync now asserts mockExistsSync is
NOT called, locking the TOCTOU removal.
- The two new exports used the @internal JSDoc tag, but this
repo's existing pattern for "public-by-convention-for-tests"
helpers (e.g. RegistryService.ts:481) is plain prose
"Exposed for unit tests." Switched to that style.
No behavior change beyond the TOCTOU removal.