Repository navigation
Conversation
This was referenced Oct 17, 2026
samgbafa
force-pushed
the
skgbafa/tc-887-s3b-fs-sealed
branch
from
October 17, 2026 12:30
cc0e223 to
91a2a57
Compare
samgbafa
force-pushed
the
skgbafa/tc-887-s3-kv-sealed
branch
from
October 17, 2026 12:30
90fd759 to
21aa0e9
Compare
Contributor
Author
|
Rebased onto S1 round 3 (#327 daef3cf). Exact-head CI on 91a2a57 (dispatched with workflow_dispatch on the PR branch): run https://github.com/TinyCloudLabs/tinycloud-node/actions/runs/39032818172, all jobs green. |
Contributor
Author
|
REQUEST_CHANGES: see TC-887 (head |
Contributor
Author
|
REQUEST_CHANGES: see TC-887 (head 91a2a57) |
samgbafa
added a commit
that referenced
this pull request
Oct 17, 2026
…e-lock guards (#326) - allow_resplit_before_verify no longer re-splits the same ARK: the node makes a fresh ARK (generation 1), splits it for the configured holders and retires the old one, so old shares rebuild only a key that never protected anything. - A W-authenticated tombstone per old ARK goes to the mirror first; load() then drops that ARK's key and escrow rows (also from a database restored to before the retirement) and ignores and deletes its ARK, escrow and record objects. Forged or junk tombstones are skipped; a database mixing retired and live rows is Inconsistent. - The database swaps keys in one transaction (replace_all_keys). - may_resplit also reads the mirror: any record- or dek- object, or any stored record, refuses the shortcut. - KeyMirror::delete (MemoryMirror implements it). - Takes S3's #333 keyring changes (efa5bbb): write_locked / in_write_lock, DataKeyError::NotPrepared, the guards on restore_dek, mirror_dek and create_dek, DataKeyProvider::prepare_read and its test. D1 still holds: prepare_read's restore fails closed on a mirrored DEK that doesn't authenticate.
Contributor
Author
|
APPROVE: see TC-887 (head |
Contributor
Author
|
Review round (efa5bbb): exact-head CI run https://github.com/TinyCloudLabs/tinycloud-node/actions/runs/39045002838 — all jobs green. |
Contributor
Author
|
APPROVE: see TC-887 (head efa5bbb) |
samgbafa
force-pushed
the
skgbafa/tc-887-s3-kv-sealed
branch
from
October 17, 2026 16:52
21aa0e9 to
843cea8
Compare
samgbafa
force-pushed
the
skgbafa/tc-887-s3b-fs-sealed
branch
from
October 17, 2026 16:52
efa5bbb to
4dbf283
Compare
samgbafa
force-pushed
the
skgbafa/tc-887-s3-kv-sealed
branch
from
October 17, 2026 17:33
843cea8 to
d05c261
Compare
samgbafa
force-pushed
the
skgbafa/tc-887-s3b-fs-sealed
branch
from
October 17, 2026 17:33
4dbf283 to
8ba1e92
Compare
Contributor
Author
|
APPROVE: see TC-887 (head 8ba1e92) |
Contributor
Author
|
Rebased onto S1 #327 029c703. Exact-head CI on 8ba1e92: run https://github.com/TinyCloudLabs/tinycloud-node/actions/runs/39054264467, all jobs green. |
…before the invoke lock
The FileSystem block store follows the at-rest mode and keys: sealed
writes under v1/{space_tag}/{object_tag} (in-memory and temp-file
staging; atomic rename), reads and ranges through the read plan
(sealed, then legacy unless strict; 503 while locked), plaintext sizes
by tag, sealed removal. lib.rs links the store to AtRestRuntime; an S3
block store refuses a non-off mode until it can seal.
New ImmutableWriteStore::prepare_write hook, which invoke calls for
each written space before the SQLite writer lock and transaction; the
FS store creates the space's data key there. Invariant: no key-store
I/O inside invoke's writer lock or transaction (a DEK insert there
deadlocked SQLite nodes on the first sealed put).
Box the decrypting reader inside FsReadable and the sealed read, range and persist futures behind the async-trait methods. The larger inline futures overflowed the 2 MiB test-thread stack in policy_v3::tests::policy_owner_may_be_a_session_the_space_owner_delegated_to.
On S1 round 3, at_rest::setup refuses a non-off mode until storage declares a seal path. storage::attach_at_rest declares it for the FileSystem store, which seals every write; S3 declares none yet, so its own refusal goes and setup's applies. The ceremony test now expects encrypt and strict to boot and encrypt with a pinned, verified escrow; the docs no longer say no storage seals. Drops the duplicate parking_lot dev-dependency.
…te lock; keys only looked up under it (S3 review)
…ed-space put test; changelog (S3 review deltas)
samgbafa
force-pushed
the
skgbafa/tc-887-s3b-fs-sealed
branch
from
October 17, 2026 18:48
8ba1e92 to
a6201d0
Compare
samgbafa
force-pushed
the
skgbafa/tc-887-s3-kv-sealed
branch
from
October 17, 2026 18:48
d05c261 to
8acfd5d
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of TC-887 (encryption at rest), slice S3b: the FileSystem block store seals KV blocks. With at-rest mode
off(the default), the store behaves as it does today.Stack: base
skgbafa/tc-887-s3-kv-sealed(#329) → #327/#326/#325 (S1 round 3). The diff below is S3b only. CI runs byworkflow_dispatchon the PR branch (see comments).Touches core's invoke path (
tinycloud-core/src/db.rs,storage/{mod,either}.rs): a new hook runs before the writer lock. Please review that part with the invariant below in mind.What changed
ImmutableWriteStore::prepare_write(space)(core): a new trait method, default no-op, delegated byBoxandEither. The invocation path calls it for each space it writes, before the SQLite writer lock and the database transaction.persiststill runs inside both. Aprepare_writeerror fails the request before the transaction.storage/file_system.rs):prepare_writecreates or loads the space's current data key (503 if keys are locked or encryption is off in a non-offmode).write_target. Sealed blocks go tov1/{space_tag}/{object_tag}, from either staging type: memory →seal_vec→ temp file → atomic rename; temp file →seal_file→ atomic rename. Only the sealed name dedups.read_plan: sealed, then legacy (refused instrict; a legacy miss while keys are locked → 503). TheReadabletype becomesEither<plaintext reader, DecryptReader>.removetakes the sealed copy first.containsfollows the read plan.lib.rs,storage/mod.rs):storage::attach_at_restlinks the opened store toAtRestRuntimethroughsealed::AtRestHandle, a per-operationAtRestSource, and calls S1'sdeclare_seal_path()for the FileSystem store. The S3 block store doesn't seal yet and declares no seal path, soat_rest::setuprefuses a non-offmode with S3 storage rather than write plaintext; S3c adds S3. S1's ceremony test now expectsencrypt/strictto boot and encrypt on an FS node with a pinned, verified escrow, and the nodeat_rest.rsdocs anddocs/at-rest-escrow.mddrop the "no storage seals" wording (agreed with S1).did_resolution_single_entry: the FS store's existingrange.resolve(total_size)moved intorange_legacy, so its allowlist entry is renamed to match.tinycloud-node-serverdev-dependencyparking_lot(already in the lock), for a switchable test state.How it's tested
db::test::prepare_write_runs_before_the_writer_lock_and_persist_inside_it, on a SQLite file DB. A probe store records whether the writer lock was free in each hook: it requiresprepare_writewith the lock free, thenpersistwith it held. Withpersistfailing, the transaction rolls back after a successful prepare and leaves no KV row. Whatever prepare created stays unused; for real keys that's a DEK row the next put reuses. Without the hook the probe sees onlypersist, and the test fails.tests/at_rest_fs_node.rs: an FS node on a SQLite file DB withat_rest = {mode: "encrypt", escrow_none: true}, driven through/delegateand/invokewith SDK sessions.v1/…and TCE1, with no marker, space name or address in them.offnode stores the plaintext under the legacy name.storage/file_system/sealed_tests.rs, 10):offwith keys still writes legacy;encryptand refused instrict(500);cargo test -p tinycloud-node --lib storage::(27),-p tinycloud-core --lib storage::,did_resolution_single_entryand theat_rest_harnessself-test pass.cargo clippy -p tinycloud-core -p tinycloud-node --all-targets -- -D warningsandcargo fmt --all --checkare clean.Review fixes (security 3c9d19cf, correctness d6a8c93b), commit
0cc5ce4authorize_admitted/verify_and_authorize_with_graph, as on the read-only path) and its spaces' hosting is checked before any prepare hook runs, so a refused request creates no data key or mirror object. The transaction still authorizes as before.ImmutableReadStore::prepare_read(→DataKeyProvider::prepare_read: restores a lost DEK from its authenticated mirror, never creates one), and written spaces also getprepare_write;get_kv/persistrun inat_rest::write_locked, where the keyring answersNotPrepared(503) instead of any key-store write;ImmutableReadStore::{prepares, prepare_read}. The key-provider side (DataKeyProvider::prepare_read,DataKeyError::NotPrepared,write_locked, and the keyring guards) now lives in S1 feat(TC-887): at-rest keyring, key store and migration #326, unchanged.KeyInconsistent(500).LoggedDecrypt).containschecks only the name a write would use (INTERFACES §7).seal_vecpanics areInternal(500), and the in-memory sealed write is async.remove.at_rest_fs_node:a_refused_put_creates_no_data_key_or_mirror_object;a_mixed_read_and_delete_after_losing_the_data_key_row_completes, which hung 30 s before the fix.storage_prepares_hosted_spaces_before_the_writer_lock_and_persists_write_locked.under_the_write_lock_keys_are_only_looked_up.containsper §7;/invokereads and writes, and reason codes.Delta round (security 6d578564, correctness 3f0b1aec), commit
a6201d0, on S1 final #327c1a040ean_unauthorized_put_to_a_hosted_space_creates_no_data_key. A session with onlykv/getputs to its own hosted space: 401/403, 0 DEK rows, 0 mirror objects. It fails RED with the pre-check authorization removed (the never-hosted test does not), and is GREEN with it.object = v1/{space tag}/{object tag}(keyed hashes only), including mid-stream ones. The route-level logging without a locator is gone.AtRestError::DekUnavailable(S1) maps to a 503 with its reason code.