Stage 12 of the runtime redesign landed FileBackedEpochStore and FileBackedNonceStore (commit 2fddbef) with 11 unit-level tests covering roundtrip, monotonic rollback denial, corruption, and atomic persist. The stores are correct in isolation but are not yet wired into any production call path.
What's missing
Epoch store
runtime/src/vm_stub_entry.cpp still passes RuntimeEpochState{2, 1} as hard-coded numbers when calling accept_package. Across process restarts, minimum_accepted_epoch resets to 1, breaking the anti_downgrade_epoch monotonic guarantee from internal research doc 06 §10. A previously-advanced floor is lost on reboot, so an old package whose anti_downgrade_epoch is behind the in-memory floor gets re-accepted.
Nonce store
runtime/src/tokens.cpp's accept_reprovision_token / accept_migration_token take NonceStore& by reference. The only caller is test_tokens.cpp, which uses InMemoryNonceStore. No production site exists yet. When a production caller lands, it would currently lose every consumed nonce on process exit, breaking the replay-protection rules in internal research doc 10 §6.3 and doc 15 §9 #9.
Why this is not "just fix the call site"
The wiring patch is small (~20 lines + a process-level singleton for the two stores + a config knob for the path). But it requires decisions that belong in an installer spec, which does not exist yet:
- Where does the file live? per-user (
%LOCALAPPDATA% / $XDG_STATE_HOME / ~/Library/Application Support/)? per-machine? inside the artifact bundle (wrong — defeats per-install replay protection)? an install-time-seeded runtime data directory?
- Who owns the path? CLI override for tests / debug? env var? compile-time constant? install-time parameter written to registry / plist / sysctl?
- First boot (file missing): current behavior is
EpochState{0, 0} treated as fresh state, i.e. the first signed package seen by a fresh install sets the floor. Probably correct, but needs to be stated as policy rather than an accidental default.
- Corrupt-file policy:
StoreError::Corrupt is returned to caller; should the runtime fail-closed or re-seed with fresh state?
- Multi-process concurrency:
std::mutex protects a single process. Across processes, atomic rename preserves file consistency but "latest write wins" loses a writer's consumed nonces.
- Upgrade path: no v2 format migration story yet.
Full write-up in internal research doc 17 (runtime local-state wiring).
Suggested PR sequence (once installer spec lands)
- P1:
runtime_init() layer instantiates both stores (test path uses in-memory, production uses file-backed behind a feature flag).
- P2: production path reads env var / config for store location;
FileBackedEpochStore / FileBackedNonceStore hooked in.
- P3: installer
seed() entry points on both stores.
- P4: store format v2 + migration hook.
Blocked on
- Installer / deployment spec (does not exist).
Out of scope
- Changing the Stage 12 store APIs themselves.
- Highsec-specific TPM-sealed store (separate issue when we're ready to talk about provider-backed storage per internal research doc 14 §10).
Stage 12 of the runtime redesign landed
FileBackedEpochStoreandFileBackedNonceStore(commit2fddbef) with 11 unit-level tests covering roundtrip, monotonic rollback denial, corruption, and atomic persist. The stores are correct in isolation but are not yet wired into any production call path.What's missing
Epoch store
runtime/src/vm_stub_entry.cppstill passesRuntimeEpochState{2, 1}as hard-coded numbers when callingaccept_package. Across process restarts,minimum_accepted_epochresets to1, breaking theanti_downgrade_epochmonotonic guarantee from internal research doc 06 §10. A previously-advanced floor is lost on reboot, so an old package whoseanti_downgrade_epochis behind the in-memory floor gets re-accepted.Nonce store
runtime/src/tokens.cpp'saccept_reprovision_token/accept_migration_tokentakeNonceStore&by reference. The only caller istest_tokens.cpp, which usesInMemoryNonceStore. No production site exists yet. When a production caller lands, it would currently lose every consumed nonce on process exit, breaking the replay-protection rules in internal research doc 10 §6.3 and doc 15 §9 #9.Why this is not "just fix the call site"
The wiring patch is small (~20 lines + a process-level singleton for the two stores + a config knob for the path). But it requires decisions that belong in an installer spec, which does not exist yet:
%LOCALAPPDATA%/$XDG_STATE_HOME/~/Library/Application Support/)? per-machine? inside the artifact bundle (wrong — defeats per-install replay protection)? an install-time-seeded runtime data directory?EpochState{0, 0}treated as fresh state, i.e. the first signed package seen by a fresh install sets the floor. Probably correct, but needs to be stated as policy rather than an accidental default.StoreError::Corruptis returned to caller; should the runtime fail-closed or re-seed with fresh state?std::mutexprotects a single process. Across processes, atomic rename preserves file consistency but "latest write wins" loses a writer's consumed nonces.Full write-up in internal research doc 17 (runtime local-state wiring).
Suggested PR sequence (once installer spec lands)
runtime_init()layer instantiates both stores (test path uses in-memory, production uses file-backed behind a feature flag).FileBackedEpochStore/FileBackedNonceStorehooked in.seed()entry points on both stores.Blocked on
Out of scope