tidaldb/docs/planning/milestone-11/guarantee-traceability.md
jx12n 1b5bcbacd7 fix(net): classify ship deadline as timeout, not partition (write-burst false-partition)
A client-side ship DEADLINE means the RPC did not round-trip within
request_timeout — which a slow-but-ALIVE follower produces under a sustained
1536-D ack=quorum apply burst (transport runtime momentarily starved by the
CPU-heavy HNSW apply on its single segment-receiver thread) exactly as a
genuinely blackholed peer does. Counting that as record_failure was the
write-burst false-partition: 5 such opened both followers' breakers, the commit
index stalled, ack=quorum 503'd, and retries re-burst the same starved peers
with no self-heal.

- CircuitBreaker::record_timeout: opens ONLY when the peer shows no recent proof
  of life (no round-tripped success/backpressure within reset_duration); neutral
  no-op when liveness is fresh; re-opens (never wedges) HalfOpen; never refreshes
  the liveness stamp (no reply arrived).
- PeerPool::send_to routes tonic DeadlineExceeded/Cancelled -> record_timeout;
  genuine severance still surfaces as connect-level Unavailable/transport reset
  -> record_failure and still opens the breaker.
- ship_timeout_breaker.rs: end-to-end proof over a REAL tonic WalShipping server
  (handler succeeds once then hangs past the client deadline) + 6 unit tests.

Also: re-scope G-S Scalability guarantee to read-throughput with the Ref-A
tidal-t5-readtput owner-test (write 2.5x is structurally impossible on 3-node
full-placement RF3); bump k8s image to m12-rc6 (live, commit 0919b0a); rustfmt
soak_eval / soak-eval / s3 / tidalctl.
2026-06-19 16:25:50 -06:00

78 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Enterprise-Guarantee → Automated-Test Traceability (m11p9)
This is the m11p9 exit-gate artifact: **every guarantee in
[roadmap-to-cluster.md §2](../../roadmap-to-cluster.md) maps to a named automated
test** (the second half of the GA bar is the calendar one — the nightly suite
green for 30 consecutive days; see [phase-9.md](phase-9.md)).
Each guarantee lists the named test(s) that prove it, the fault class each one
injects (where applicable), and the first-class invariant checker
([`tidal-server/tests/support/invariants.rs`](../../../tidal-server/tests/support/invariants.rs))
it asserts through. All `mp_*` tests are tier-3 over REAL OS processes
(`MultiProcCluster`); partitions sever real loopback TCP; the disk-full /
slow-fsync faults return a real `ENOSPC` / sleep the real fsync behind the
`fault-injection` feature (`tidal/src/fault.rs`).
Run any suite locally:
`cargo test -p tidal-server --features "cluster-e2e fault-injection" --test <suite> -- --test-threads 1 --nocapture`.
Every named test that EXISTS below runs nightly via the cron pipeline in
[`.woodpecker.yaml`](../../../.woodpecker.yaml): the chaos/correctness suites and
the gated soak in `nightly-chaos`/`nightly-soak`, and the G-Sec/G-Op owner-tests
(mTLS, `cluster_security`, `tidalctl` backup/restore, WAL-archival) in
`nightly-security-ops`. The one exception is the **G-S** throughput gate, which
is not yet written (m11p6 L4) — its row is marked accordingly.
| Guarantee | Named automated test(s) | Suite | Fault / scenario | Invariant checker |
|---|---|---|---|---|
| **G-D Durability**`ack=quorum` 2xx ⇒ majority-durable; SIGKILL any node ⇒ zero acked loss | `mp_quorum_ledger_zero_acked_loss_across_killpoints` | `cluster_quorum` | SIGKILL leader across N random kill-points under quorum load | `AckLedger` (frontier + content) |
| | `mp_auto_failover_writes_resume_zero_acked_loss` | `cluster_election` | SIGKILL leader, auto-elect, across kill-points | `AckLedger` |
| | `mp_disk_full_follower_degrades_no_acked_loss` | `cluster_faults` | **disk-full** (ENOSPC) on a follower | `AckLedger` + `MonotonicCounters` |
| | `mp_slow_fsync_follower_lags_but_quorum_holds`, `mp_slow_fsync_both_followers_force_honest_quorum_timeout` | `cluster_faults` | **slow-fsync** on one / both followers | `AckLedger` |
| | `mp_follower_reseeds_via_snapshot_after_compaction`, `mp_quarantined_node_reseeds_without_wipe` | `cluster_reseed` | WAL compaction past cursor / divergent-suffix quarantine | content probes |
| **G-A Availability** — single-node loss ⇒ reads continue, writes resume <10 s, no dual-leader | `mp_auto_failover_writes_resume_zero_acked_loss` | `cluster_election` | leader SIGKILL election (<10 s) | `AckLedger` |
| | `mp_fenced_ex_leader_restart_cannot_write` | `cluster_election` | partition leader away, restart while fenced | fencing assertions |
| | `mp_flapping_links_bounded_churn` | `cluster_election` | repeated link flaps | single-leader-per-term |
| | `mp_asymmetric_partition_no_split_brain_no_loss` | `cluster_faults` | **asymmetric partition** (inbound severed, outbound up) | `assert_single_leader_per_term` + `AckLedger` |
| | `mp_self_heal_converges_without_operator_verb` | `cluster_chaos` | gRPC link sever, no operator verb | feed parity |
| **G-S Scalability** _(re-scoped 2026-06-19 — read-throughput)_ read throughput scales with node count at RF=3 **full placement**: every node serves `/vector_search` from its LOCAL replica (no cross-node read forward), so aggregate read-tput N × single-node within the 10 ms p99 / recall 0.95 SLA. _Write-tput 2.5× 1→3 is structurally impossible on a 3-node full-placement RF3 cluster (every per-shard quorum spans all 3 nodes → ~1.0× write-scaling), proven in `docs/profiling/m12p4-t5-sharded-throughput.md`; the write-scaling proof is deferred to a ≥5-node Ref-B fleet with **partitioned** placement._ | `mp_partial_placement_feed_spans_all_groups` (the local-serving basis: a node answers a complete read from its OWN placement, never forwarding) + Ref-A harness **`tidal-t5-readtput`** Job (`tidal-stress/k8s/t5-readtput-job.yaml`) | `cluster_cross_shard_reads` (cargo) / Ref-A operational | local-scatter read fan-out; round-robin read ramp 10003800 rps across all 3 nodes | local-scatter completeness + read p99 10 ms / recall 0.95 SLA gates |
| **G-E Elasticity** online add/remove/replace; snapshot+stream catch-up; p99 <2× for <60 s | `mp_scale_3_5_3_under_load_zero_loss`, `mp_seed_join_snapshot_catchup`, `mp_dns_hostname_topology_replicates` | `cluster_membership` | scale 353 under load; seed-join after compaction | `AckLedger` (lost=0), p99 bound |
| **G-Sec Security** all inter-node links mTLS; authenticated RPC; rotation no downtime; admin audit | `http_tls_serves_ca_trusting_client_and_rejects_foreign`, `http_tls_cert_rotation_under_load_drops_zero` | `cluster_security` | foreign-CA client rejected; cert hot-rotation under load | zero-drop assertion |
| | `mtls` (foreign-pod ship rejected), `cluster::security` unit tests (token mint/verify, marker-not-bypass) | `tidal-net/tests/mtls.rs`, engine unit | foreign pod cannot ship or call internal routes | negative tests |
| **G-O Observability** per-node Prometheus metrics; dashboard answers golden signals; alerts on lag / commit stall / election churn | per-node `/metrics` listener + `tidaldb_cluster_*` set (m11p8); `grafana-dashboard.json` "Cluster Replication" row (12 panels); `prometheus-alerts.yaml` `tidaldb-cluster` group (8 rules) | `cluster_runbook` (`runbook_s5_health_and_openapi`) + artifacts | metric/alert presence | artifact + status tests |
| **G-Op Operability** rolling upgrade N/N+1 under load (CI gate); backup/restore + PITR drilled; runbooks executable | `mp_rolling_upgrade_no_loss_no_stall` (the **Woodpecker release gate**) | `cluster_lifecycle` | graceful SIGTERM version-tagged restart heal fixpoint under load | `AckLedger` |
| | `backup_then_restore_roundtrips`, `restore_rejects_corrupted_backup`, `online_compaction_archives_before_deleting` | `tidalctl` / engine | offline backup/restore integrity; gap-free WAL archival (PITR) | BLAKE3 manifest |
| | `runbook_s5..s11` (full operator playbook) | `cluster_runbook` | failover / partition / shutdown+recovery drills | per-drill |
| **G-C Continuous correctness** nightly chaos + soak green 30 consecutive days | the entire `cluster_faults` suite + the `nightly-chaos`/`nightly-soak` cron pipeline + `tidal-stress` soak regression gates + THIS matrix | `cluster_faults` / `.woodpecker.yaml` / `tidal-stress` | all fault classes nightly; soak p99/error regression gates | all four checkers |
## Honest status of the bar
- **G-D, G-A, G-E, G-Sec, G-Op** each maps to a green named test that runs in
the nightly pipeline.
- **G-S (Scalability)** **RE-SCOPED 2026-06-19 to read-throughput** and now
owner-tested. The original write-throughput bar (≥2.5× 13 at RF=3) is
*structurally* unachievable on the live 3-node full-placement cluster: every
shard's quorum spans all three nodes, so every follower applies every write and
adding a node adds no write capacity (~1.0×, measured in
`docs/profiling/m12p4-t5-sharded-throughput.md`). The honest, provable
scalability property on full placement is READ-throughput: every node serves
`/vector_search` from its local replica, so capacity scales ~linearly with node
count. Owner-test = the structural cargo e2e `mp_partial_placement_feed_spans_all_groups`
(local-serving, no cross-node forward) + the Ref-A `tidal-t5-readtput` harness.
Measured (rc12, cluster-spread across all 3 nodes): p99 7.97/11.47/9.48/9.28 ms @
100/200/300/500 rps, recall@10 0.9989, 0.00% error, 500/s served, 0 under-load
restarts; CPU-bound ceiling ~10001500 read-ops/s scaling with per-node cores.
(read-tput, Ref-A). The 2.5× **write**-scaling proof is deferred to a 5-node
Ref-B fleet with partitioned placement. (Ref-B hardware.)
- **G-O (Observability)** proven by artifact + presence tests (the metric set,
the dashboard, the alert group) rather than one behavioral test; the
golden-signal coverage is the m11p8 exit-gate evidence.
- **G-C (Continuous correctness)** the *apparatus* is complete and green
(the fault suite passes; the cron pipeline runs every named suite + the gated
soak). The remaining half of the bar is **calendar**: 30 consecutive nightly-green
days before GA. That cannot be completed in one session the pipeline is what
produces it. (accrues nightly.)
- **Ref-A line items** the throughput / 100k-item / 1-hour-soak figures that
must run on the live 3-region k3s cluster remain blocked on k3s access (the
standing M11 caveat from m11p1p3); the in-runner soak gates regressions
locally and the same `tidal-stress` invocation targets Ref-A when reachable.