Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
# TC-887: run the full Rust CI on a stacked branch (a PR whose base is not
# main gets no pull_request run). Manual only; same jobs as a PR to main.
workflow_dispatch:

env:
CARGO_TERM_COLOR: always
Expand Down Expand Up @@ -243,8 +246,8 @@ jobs:
# database_artifact (checkpoint and WAL delta). The RED checks assert
# encryption at rest; until TC-887 S3/S4 land, the step passes only if both
# fail with their "TC-887 RED:" findings (`--expect red`), and fails loudly
# if they error or unexpectedly pass. Switch to `--expect green` (with the
# at-rest mode set) when S3/S4 merge.
# if they error or unexpectedly pass. When S3 lands, start the harness with
# the at-rest mode set and expect `kv=green,sql=red`; when S4 lands, `green`.
at-rest-harness:
name: at-rest harness (native PostgreSQL + MinIO)
runs-on: ubuntu-latest
Expand Down
292 changes: 292 additions & 0 deletions docs/at-rest/INTERFACES.md

Large diffs are not rendered by default.

47 changes: 30 additions & 17 deletions scripts/at-rest-harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ in logs. `state.json` (ports, PIDs and start times, binaries, log paths,
```sh
STATE=$(scripts/at-rest-harness/harness.sh up) # builds the node (nice -n 10, -j 8); prints the state path last
scripts/at-rest-harness/harness.sh test green "$STATE" # writes reach MinIO and PG (passes)
scripts/at-rest-harness/harness.sh test red --expect red "$STATE" # exit 0 only if both RED checks fail as RED
scripts/at-rest-harness/harness.sh test red --expect red "$STATE" # exit 0 only if both RED checks (kv, sql) fail as RED
scripts/at-rest-harness/harness.sh down --purge "$STATE" # stop this run's processes, remove its dir
```

Expand All @@ -45,8 +45,10 @@ run dir). Nothing defaults to a "latest" run, so concurrent runs on a shared
host can't act on each other.

- `up --no-build` reuses an already-built binary (or set `TC887_NODE_BIN`).
- If `up` fails, times out or is interrupted, it stops every process it
started. It keeps the run dir with its logs; remove it with `down --purge`.
- If `up` fails, times out (SIGTERM) or is interrupted (SIGINT), it stops
every process it started. It keeps the run dir with its logs; remove it with
`down --purge`. A SIGKILL of `up` can't be trapped: then run `down STATE`
(the state file is written after each start).
- `status STATE` prints the state file and `logs STATE` tails the logs.
- Overrides: `TC887_SCRATCH_ROOT`, `TC887_RUN_DIR` (must be named
`harness-run-*`), `TC887_{PG,MINIO,MINIO_CONSOLE,NODE}_PORT`, `TC887_BIN_DIR`,
Expand All @@ -55,30 +57,37 @@ host can't act on each other.
take one before the harness does. A collision makes `up` fail cleanly; it
can't produce a false green, because the checks talk to MinIO and PG directly.

`test` exits 0 only on the expected outcome:
`test` runs each check (`kv`, `sql`) on its own and compares each with its
expectation. `--expect` takes `green` or `red` (both checks) or a per-check
list such as `kv=green,sql=red`; a check left out keeps its default. `test`
exits 0 only if every check matches:

| Exit | Meaning |
|---|---|
| 0 | as expected (`GREEN as expected` / `RED as expected`) |
| 3 | unexpected result: RED checks passed (`UNEXPECTEDLY PASSED for KV/SQL`), or `--expect green` while still RED |
| 4 | errored: compile failure, harness failure, a precondition (`harness no-op`/`harness gap`) or a node error, i.e. not a RED finding |
| 0 | every check as expected (`KV RED as expected` … / `RED as expected: both …`) |
| 3 | a check didn't match: `KV red check UNEXPECTEDLY PASSED under expect kv=red`, or `KV red check still RED … but expected kv=green` (per check) |
| 4 | a check errored: compile failure, harness failure, a precondition (`harness no-op`/`harness gap`) or a node error, i.e. not a RED finding (per check; wins over 3) |

`--expect` defaults to `green` for `test green`. For `test red` it defaults to
`red`, or to `green` if the run was started with `TC887_AT_REST_MODE=encrypt`
or `strict`.
Defaults: `test green` expects `kv=green,sql=green`. `test red` expects
`kv=red,sql=red`, or `kv=green,sql=red` if the run was started with
`TC887_AT_REST_MODE=encrypt` or `strict`, because S3 seals KV before S4 seals
SQL. When S4 lands, the SQL default flips to green (`default_expect` in
`harness.sh`). CI runs `test red --expect red` until S3 lands.

## At-rest mode

The config names belong to the S1 key-continuity slice and may change. They
are defined in one marked block at the top of `harness.sh`:
The names are S1's (key continuity). They are defined in one marked block at
the top of `harness.sh`:

| Harness env (at `up`) | Node env written to `node.env` |
|---|---|
| `TC887_AT_REST_MODE` (`off`/`encrypt`/`strict`) | `TINYCLOUD_AT_REST__MODE` |
| `TC887_AT_REST_ESCROW` (e.g. `none`, the Static self-host acknowledgement, plan §5) | `TINYCLOUD_AT_REST__ESCROW` |
| `TC887_AT_REST_ESCROW_NONE` (`true`/`false`: the Static self-host acknowledgement that no escrow exists, plan §5) | `TINYCLOUD_AT_REST__ESCROW_NONE` |

If either is unset, the harness sets nothing and the node runs its default
(today: no encryption at rest).
(today: no encryption at rest). Other values are refused before anything
starts. The simplest encrypting node is the harness's Static root plus
`TC887_AT_REST_MODE=encrypt TC887_AT_REST_ESCROW_NONE=true`.

## Checks

Expand All @@ -98,17 +107,21 @@ then insert (a WAL delta).
|---|---|---|
| `green_kv_put_reaches_minio` | every put round-trips through the node and adds a non-empty object to the bucket | pass |
| `green_sql_write_reaches_postgres` | the writes round-trip, and `database_artifact` has a `payload` and a non-NULL `delta_payload` | pass |
| `red_kv_blocks_are_sealed_in_minio` | object keys contain no space id, wallet address or blake3 fingerprint of the value (b64url padded/unpadded, base64 padded/unpadded, hex; raw digest and multihash); raw bytes hold no marker; the 64 KiB object doesn't compress (zlib ratio > 0.95); the two spaces' objects for the same value share no 32-byte run | **fail** |
| `red_sql_artifacts_are_sealed_in_postgres` | `payload` doesn't start with `SQLite format 3\0` and `delta_payload` doesn't start with the WAL magic; neither holds a marker or compresses (ratio > 0.95); the two spaces' payloads and deltas share no 32-byte run | **fail** |
| `red_kv_blocks_are_sealed_in_minio` | object keys contain no space id, wallet address or blake3 fingerprint of the value (b64url padded/unpadded, base64 padded/unpadded, hex; raw digest and multihash); raw bytes hold no marker; each put's objects together are at least as large as the value; the 64 KiB object doesn't compress (zlib ratio > 0.95); every object of a put in one space shares no 32-byte run with every object of the same put in the other (equal object counts required) | **fail** |
| `red_sql_artifacts_are_sealed_in_postgres` | `payload` doesn't start with `SQLite format 3\0` and `delta_payload` doesn't start with the WAL magic; neither holds a marker, compresses (ratio > 0.95) or is smaller than the 64 KiB value it holds; the two spaces' payloads and deltas share no 32-byte run | **fail** |

The RED checks re-run the green preconditions first, so a RED result is never
a harness no-op. The 32-byte limit: two sealed blobs of the same plaintext
share only fixed TCE1 header fields (≤ 20 contiguous bytes), while any
deterministic transform (encoding, XOR, compression, an unsalted cipher) keeps
runs as long as the data. The reasoning is in the test's `MAX_SHARED_RUN` doc.
The size floor holds because TCE1 never shrinks data. It catches strong
compressors (xz, bz2) whose output neither compresses further nor repeats
across writes once a per-write salt (SQLite's WAL salts) is mixed in.
`selftest_predicates_flag_deterministic_transforms_not_ciphertext` runs in plain
`cargo test` and checks the predicates offline: base64, XOR and zlib are
flagged, and sealed-like random blobs are not.
flagged, a salted deflate-9 blob is flagged by the size floor, and sealed-like
random blobs pass.

## Not covered

Expand Down
Loading
Loading