feat(core): persist key-epoch records and enforce the cold-start key-epoch check (#445) - #945
Conversation
…epoch check (#445) write_key_epoch persists generation-addressed key-epoch records under the root key route; load_key_epoch_set reads each epoch's newest generation from a gap-free chain. commit_root (before step C) and load_committed_root enforce contract §5.3.1 step 5: the set hashes to the root's key_epoch_set_digest and active_key_epoch is exactly one KEY_EPOCH_ACTIVE record binding the root's key route. DurableFs gains create_dir_all.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 38 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 82 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Review configuration: ⚙️ Run configurationConfiguration used: Repository: qnbs/WorldScript-Studio/.coderabbit.yaml Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe secure-storage crate now writes generation-addressed key-epoch records and validates the persisted set against root data during commits and cold starts. Tests cover ordering, set and route mismatches, tampering, and gaps. Changelog and native storage records document the implemented slice and remaining work. ChangesKey-epoch persistence and root validation
Priority: ➖ Normal Merge Risk: 🔵 Low · up to The new key-epoch records can lose their directory entry after a power loss, which would leave a later startup requiring recovery. Production authority is not switched by this change, so this is a bounded follow-up. Add the parent directory sync before relying on this path.
Comment |
Reviewer's GuideThis PR persists authenticated key-epoch record generations and makes the root's key-epoch set—and its active epoch's binding to the committed key route—a required fail-closed invariant during both root commits and cold starts, with focused tests and Gate 3C documentation updates. Sequence diagram for key-epoch persistence and root commit validationsequenceDiagram
participant Caller
participant RootStore
participant DurableFs
participant KeyProvider
participant Staging
Caller->>RootStore: write_key_epoch()
RootStore->>DurableFs: create_dir_all()
RootStore->>DurableFs: list_dir()
RootStore->>KeyProvider: resolve_ref()
RootStore->>Staging: stage_and_promote()
Staging-->>RootStore: KeyEpochEntry
RootStore-->>Caller: KeyEpochEntry
Caller->>RootStore: commit_root()
RootStore->>RootStore: load_key_epoch_set()
RootStore->>KeyProvider: resolve_ref()
RootStore->>DurableFs: list_dir() and read()
RootStore->>RootStore: verify_key_epochs()
RootStore->>RootStore: prepare_root_anchor()
RootStore-->>Caller: committed root or recovery error
Entity relationship diagram for persisted key-epoch generationserDiagram
KEY_EPOCH_SET {
digest key_epoch_set_digest
}
KEY_EPOCH_RECORD {
uint64 epoch
uint64 registry_generation
digest content_digest
status status
digest root_key_ref_digest
}
KEY_EPOCH_SET ||--o{ KEY_EPOCH_RECORD : contains
KEY_EPOCH_RECORD ||--o{ KEY_EPOCH_RECORD : forms_gap_free_generation_chain
State diagram for trusted root loading with key-epoch checksstateDiagram-v2
[*] --> ReadCommittedRoot
ReadCommittedRoot --> LoadKeyEpochSet
LoadKeyEpochSet --> RecoveryRequired: unexpected name, gap, or record fails to open
LoadKeyEpochSet --> VerifyKeyEpochs
VerifyKeyEpochs --> RecoveryRequired: set digest mismatch
VerifyKeyEpochs --> RecoveryRequired: active epoch not bound
VerifyKeyEpochs --> TrustedRoot: exactly one KEY_EPOCH_ACTIVE matches root route
TrustedRoot --> [*]
RecoveryRequired --> [*]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| Docker | Oct 2, 2026 3:28a.m. | Review ↗ | |
| Python | Oct 2, 2026 3:28a.m. | Review ↗ | |
| Rust | Oct 2, 2026 3:28a.m. | Review ↗ | |
| Shell | Oct 2, 2026 3:28a.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
🏁 CodeAnt Quality Gate ResultsCommit: ✅ Overall Status: PASSEDQuality Gate Details
|
|
@codex review |
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
[check-pr-size] PR size is over the target tier (normal profile): 11 files, 497 meaningful lines, 3 commits — limit ≤8 files / ≤400 lines / ≤6 commits. Consider splitting into smaller, independently reviewable PRs. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/worldscript-secure-storage/tests/gate3c_root_commit_test.rs (1)
544-640: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd coverage for an uncommitted key-epoch generation.
After root generation 1 is committed,
write_key_epochcan persist generation 2 without publishing a new root. Add a cold-start test that leaves the committed root digest unchanged and assertsRootStoreError::RecoveryRequired(RootRecoveryReason::KeyEpochSetMismatch). If this check regresses, cold start may accept a key-epoch generation that the committed root does not name.Suggested test
#[test] fn cold_start_fails_closed_on_a_tampered_or_gapped_key_epoch_chain() { let mut fixture = Fixture::new(); fixture.commit(&mut StdFs, 1).unwrap(); let record = fixture .root_dir .join("key-epoch") .join("1") .join("generation-1.wsr1"); let original = fs::read(&record).unwrap(); flip_last_byte(&record); let tampered = fixture.loaded_generation(); fs::write(&record, &original).unwrap(); // A later generation without the first one is a gap, never a shorter chain. fs::rename(&record, record.with_file_name("generation-2.wsr1")).unwrap(); let gapped = fixture.loaded_generation(); let mismatch = Err(RootStoreError::RecoveryRequired( RootRecoveryReason::KeyEpochSetMismatch, )); assert_eq!((tampered, gapped), (mismatch.clone(), mismatch)); } +#[test] +fn cold_start_rejects_a_key_epoch_written_without_root_commit() { + let mut fixture = Fixture::new(); + fixture.commit(&mut StdFs, 1).unwrap(); + fixture + .write_epoch(1, KeyEpochStatus::Active, 2) + .unwrap(); + assert_eq!( + fixture.loaded_generation(), + Err(RootStoreError::RecoveryRequired( + RootRecoveryReason::KeyEpochSetMismatch, + )) + ); +} + #[test] fn key_epoch_generations_are_written_strictly_in_order() {
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: qnbs/WorldScript-Studio/.coderabbit.yaml
Review profile: CHILL
Plan: Essentials
Run ID: d2241777-36eb-47ce-9675-164d9be353fb
📒 Files selected for processing (11)
CHANGELOG.mdcrates/worldscript-secure-storage/src/commit.rscrates/worldscript-secure-storage/src/durable.rscrates/worldscript-secure-storage/src/lib.rscrates/worldscript-secure-storage/src/root_record.rscrates/worldscript-secure-storage/src/root_store.rscrates/worldscript-secure-storage/tests/gate3_durable_test.rscrates/worldscript-secure-storage/tests/gate3c_root_commit_test.rscrates/worldscript-secure-storage/tests/support/mod.rsdocs/native/CORE-MIGRATION-LEDGER.mddocs/native/R15-SECURE-STORAGE-CONTRACT.md
Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 11 files
Requires human review: Auto-approval blocked because this review re-detected 3 unresolved issues already reported by Cubic.
Re-trigger cubic
…overs; require exactly one active epoch (#445) Review wave on PR #945: write_key_epoch validates before creating anything and syncs the new epoch directory and its parents before promoting; load_key_epoch_set skips an empty epoch directory and ignores staging leftovers and relocated bytes (the root's set digest binds what counts); step 5 requires exactly one KEY_EPOCH_ACTIVE record overall, at active_key_epoch, binding the route.
There was a problem hiding this comment.
Gates Passed
3 Quality Gates Passed
See analysis details in CodeScene
Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.
User description
Part of #445 / #921 (Gate 3, slice 3C, part 3c-1). No production authority switch:
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.This resolves the deferred #944 review item, tracked on #921: bind
active_key_epochto the root's key route through the authenticated key-epoch set, never throughlist_epochs.What lands (
root_store)write_key_epoch<root_dir>/key-epoch/<epoch>/generation-<n>.wsr1through 3A's staging.nmust be exactly the next generation of that epoch, and it is sealed under the root key route.key_epoch_set_digestentry.load_key_epoch_setRECOVERY_REQUIRED.commit_root(before step C, so nothing is prepared) and byload_committed_root(cold start):key_epoch_set_digest;active_key_epochmust be exactly oneKEY_EPOCH_ACTIVErecord whose route digest is the root'sroot_key_ref_digest.DurableFs::create_dir_all, for the per-epoch directories.Proof
tests/gate3c_root_commit_test.rsnow has 18 tests. The fixture persists a realACTIVEepoch-1 record, and every root names the actual set digest. New cases:PREPARED;Locally, clippy is clean, the full crate suite passes, and
pnpm docs:checkpasses.Docs
R15_GATE3=SLICE_3C_KEY_EPOCHSis set in the block and in ledger row 10.Next: 3c-2, the write-protocol integration (catalog plus root per marker transition, dropping a rolled-back first write),
list_recordsand retention. Then Gate 3 closure with #357 and the asset-pair boundary.Summary by Sourcery
Persist and authenticate key-epoch records so root commits and cold starts fail closed unless the active epoch is bound to the root’s key route.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores:
Summary by cubic
Persists key-epoch records in the root store and enforces the §5.3.1 step 5 key-epoch check at both commit and cold start, so
active_key_epochis bound to the root's key route through the authenticated set instead oflist_epochs. No production authority switch;PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.Changes
write_key_epochstores an immutable generation-addressed record atkey-epoch/<epoch>/generation-<n>.wsr1, refuses anynthat is not the epoch's exact next generation, and leaves nothing behind on refusal.load_key_epoch_setreads each epoch's newest generation from a gap-free chain, fails closed on unexpected names, gaps, or unopenable records, and ignores crash leftovers, relocated bytes, and empty epoch directories.commit_root(before step C) andload_committed_rootrequire the set to hash to the root'skey_epoch_set_digest, with exactly one ACTIVE record atactive_key_epochbinding the root's key route; otherwiseRECOVERY_REQUIRED.DurableFsgainscreate_dir_all, and new epoch directories are synced with their parents before promotion; the gate suite grows to 18 cases covering wrong sets, PREPARED-only or duplicate active epochs, route mismatches, tampered records, chain gaps, crash leftovers, and out-of-order generations.Written for commit 9902802. Summary will update on new commits.
CodeAnt-AI Description
Persist key-epoch records and reject roots with untrusted epoch bindings
What Changed
Impact
✅ Rejects roots with tampered or incomplete key-epoch data✅ Prevents active epochs from using an unauthorized key route✅ Fails closed during unsafe cold starts💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit