Commit Graph

472 Commits

Author SHA1 Message Date
Henry Guo 0e61ba7c63 test(table-catalog): add scale fault rehearsal (#4359)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-07-07 16:37:58 +08:00
houseme 88645c9169 fix(server): stop closing idle proxy keep-alive connections + reverse-proxy hardening (#3076) (#4360)
* fix(server): raise default HTTP/1.1 idle keep-alive timeout for proxies

In hyper's HTTP/1.1 stack `header_read_timeout` is armed as soon as the
connection is ready to read the next request head, so on a keep-alive
connection it also bounds how long an idle upstream connection may sit
before RustFS closes it. The previous 5s default meant RustFS FIN'd pooled
upstream connections while reverse proxies (Caddy ~2min, Nginx 60s) still
believed they were alive. The proxy then reused a dead socket and clients
saw `socket hang up` / connection reset, most visibly on non-idempotent
`PUT` uploads that proxies will not transparently retry (issue #3076).

Raise DEFAULT_HTTP1_HEADER_READ_TIMEOUT to 75s (above common proxy
idle-keepalive windows) and document the coupling. Still overridable via
RUSTFS_HTTP1_HEADER_READ_TIMEOUT for deployments that expose RustFS
directly to untrusted slow clients and want tighter slowloris protection.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(operations): add reverse-proxy deployment guide

Document the request/body semantics RustFS expects from a proxy layer, the
idle keep-alive mismatch that causes `socket hang up` on large PutObject
writes, and known-good Caddy/Nginx configs plus Cloudflare caveats. Closes
the documentation gap called out in issue #3076 and consolidates the
scattered findings from #609/#934/#1492/#1766.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(object): bound stalled PutObject request-body reads with diagnostics

When a reverse proxy or CDN forwards a partial request body and then goes
silent without closing the connection, the inner body stream neither yields
more bytes nor reports EOF, so RustFS waited forever for bytes that never
arrive and the client saw a silent hang/abort (issue #3076). A short body
that ends with a proper EOF was already rejected promptly; the gap was the
no-EOF stall case.

Add a single-point request-body guard on the PutObject path that wraps the
incoming StreamingBlob with an inter-chunk read timeout. The timer resets on
every chunk, so slow-but-progressing uploads are unaffected; it only fires
after RUSTFS_HTTP_REQUEST_BODY_READ_TIMEOUT (default 300s, 0 disables) of
complete silence. On timeout it logs a structured `put_object_body_read_stalled`
event with received/expected byte counts and fails the read with
ErrorKind::TimedOut instead of hanging. remaining_length/size_hint are
forwarded so wrapping is transparent to downstream length handling.

Covered by unit tests for the stall path, length-preserving pass-through, and
the disabled (timeout=0) pass-through.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 15:25:31 +08:00
houseme 6f613317f6 feat(internode): optimize gRPC transport (#4337)
* feat(internode): P0 gRPC transport tuning, message limits, payload metrics

Land the P0 subtask from docs/grpc-optimization: close the client-vs-server
transport gaps and add instrumentation to size which unary RPCs need channel
isolation in P1.

Transport tuning (G3): the client `Endpoint` now disables Nagle and raises the
HTTP/2 stream/connection flow-control windows to mirror the server socket, so
small lock/health RPCs are not batched and larger metadata responses are not
throttled by the 64KiB default window. All env-overridable, 0 opts out.

Message-size limits (G1): both `NodeServiceClient` and `NodeServiceServer` set
max decode/encode size (default 100MiB) instead of tonic's silent 4MiB cap, so a
large multi-version xl.meta or aggregated ReadMultiple no longer fails
out_of_range. The server limit is set on `NodeServiceServer` before wrapping in
the auth `InterceptedService` (the interceptor type does not expose it).

Payload instrumentation (P1 prep): ReadAll/ReadMultiple record a payload-size
histogram plus a large-payload counter when a response crosses the configured
threshold (default 8MiB), feeding alerting on paths that contend with
latency-sensitive control-plane traffic on the shared channel. Threshold-only
counter, no per-call hot-path log.

Verification: cargo check/test on config, io-metrics, ecstore, rustfs; clippy
clean on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P1 control/bulk gRPC channel isolation (opt-in)

Land the P1 subtask from docs/grpc-optimization: physically separate large
bytes-carrying unary RPCs from latency-sensitive control-plane RPCs so a big
transfer can no longer head-of-line block a lock/health RPC on the shared
HTTP/2 connection (G2/G5).

Introduce ChannelClass { Control, Bulk } and get_channel_for_class in protos.
Control RPCs keep the per-peer connection keyed by the bare address; Bulk RPCs
(ReadAll/WriteAll/ReadMultiple/BatchReadVersion, via a new get_bulk_client) are
round-robined across a small per-peer bulk pool.

Rather than restructuring the global GLOBAL_CONN_MAP (and every consumer), bulk
channels are cached under a composite key (addr\0bulk\0idx). The NUL separator
cannot appear in a URL, so bulk keys never collide with the control key. This
keeps the blast radius small on a consistency-sensitive path. create_new_channel
is refactored into build_channel(dial_addr, cache_key) so several physically
distinct channels to one peer cache independently while dialing/TLS still use
the real address.

Gated by RUSTFS_INTERNODE_CHANNEL_ISOLATION (default OFF) so the default build
is byte-for-byte the pre-P1 behavior: bulk resolves to the control channel and
the switch is a single-env rollback. RUSTFS_INTERNODE_BULK_CHANNELS (default 2,
clamped >=1) sizes the pool. On failure, evict_failed_connection drops the whole
bulk pool for the peer (round-robin hides which index was used), avoiding
half-dead cached channels.

Lock RPCs (remote_locker) already use the default Control path, so lock
semantics and retry behavior are unchanged.

Verification: cargo check/test on config, protos, ecstore, rustfs; new protos
tests for bulk key routing and isolation-off passthrough; clippy clean on
touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P2 msgpack/JSON codec observability + encode buffer presizing

Land the safe, wire-compatible slice of P2 from docs/grpc-optimization: the
observability prerequisite for retiring the redundant JSON fields, plus a codec
micro-optimization. No proto/wire-format change; JSON is still dual-written.

Internode RPCs today dual-encode each metadata value as both msgpack (`*_bin`)
and a JSON compatibility string, and decoders prefer `_bin` with a JSON
fallback. Before the JSON fields can ever be dropped (a cross-version change),
that fallback must be proven unused in production.

Add rustfs_system_network_internode_msgpack_json_fallback_total{direction,
message}: incremented whenever a decode falls back to the JSON field because the
msgpack payload was absent. Wired into both directions — the client decoding
peer responses (remote_disk.rs, incl. the list-level read_multiple/batch
fallbacks) and the server decoding peer requests (node_service/disk.rs). This
counter must read zero across a release window before send paths stop writing
JSON and the proto text fields are reserved/removed (the deferred P2-1 steps).

Also pre-size the msgpack encode buffers (Vec::with_capacity(512)) on both
sides, eliminating the repeated growth reallocations for typical FileInfo
payloads with zero added copy. Full thread_local buffer pooling is deferred: it
needs either an extra copy (unclear net win) or a send-path buffer-return
lifecycle, to be justified by a codec microbenchmark first.

Verification: cargo check/test on io-metrics, ecstore, rustfs; new fallback
counter smoke test; existing codec decode tests green; clippy clean on touched
files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(internode): add msgpack/JSON convergence observation runbook

Runbook driving the observation-gated retirement of the redundant JSON
compatibility fields on internode gRPC metadata RPCs (grpc-optimization P2-1).

Documents the shipped fallback counter
(rustfs_system_network_internode_msgpack_json_fallback_total{direction,message}),
the PromQL to confirm it reads zero across a release window, a standing alert,
and the staged flip/rollback procedure (env-gated msgpack-only send, then proto
field removal in N+1).

Includes the verified field -> peer-decoder audit: only fields whose peer
decodes _bin first may be converged. Notes DeleteVersion.opts (DeleteOptions) is
NOT convergence-ready — its server handler is not _bin-first and must gain a
decode_msgpack_or_json path first. This gates the send-side change so it cannot
empty a JSON field an old peer still needs.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): env-gated msgpack-only send + DeleteVersion _bin support (P2-1)

Implements the send-side lever for retiring the redundant JSON compatibility
fields on internode gRPC metadata RPCs, plus the missing `_bin` support on the
delete path that it depends on (grpc-optimization P2-1). Default-off: the base
build is byte-for-byte the prior dual-write behavior.

Gated msgpack-only send (RUSTFS_INTERNODE_RPC_MSGPACK_ONLY, default false):
- New rustfs_protos::internode_rpc_msgpack_only() reads the flag.
- Client (remote_disk.rs) compat_json() and server (node_service/disk.rs)
  compat_response_json() emit an empty JSON string when the flag is on, so only
  the msgpack _bin payload is sent. The _bin field is always sent; decoders keep
  the JSON read fallback. Applied only to fields with a confirmed _bin-first peer
  decoder (WriteMetadata/UpdateMetadata/RenameData file_info, UpdateMetadata opts,
  ReadOptions, ReadMultipleReq, BatchReadVersionReq; ReadVersion/ReadXL/RenameData
  responses and the ReadMultiple/BatchReadVersion response lists).
- Only enable after the P2 fallback counter has read zero across a release window
  (see docs/operations/internode-msgpack-json-convergence-runbook.md). Single-env
  rollback; no wire-format break.

DeleteVersion(s) _bin support (prerequisite):
- The DeleteVersion/DeleteVersions protos had NO _bin fields. Add additive
  (backward-compatible) bytes file_info_bin/opts_bin (DeleteVersion) and repeated
  bytes versions_bin + bytes opts_bin (DeleteVersions); regenerate the checked-in
  prost struct.
- Client dual-writes them; server decodes them _bin-first with JSON fallback.
- These delete fields are kept OUT of the msgpack-only set (always dual-write)
  until their own fallback counter reads zero across a window with the new
  decoders fully deployed. DeleteVersion.raw_file_info stays JSON-only (no _bin
  field yet).

Verification: cargo check/test on protos, config, ecstore, rustfs (incl. the six
delete request handler tests and a compat_json default-path test); clippy clean
on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3 cluster peer online/offline health metric

Land the safe observability core of P3 (grpc-optimization G6/G8): track each
internode peer's reachability and expose the offline count, for parity with
MinIO's minio_cluster_servers_offline_total. Pure instrumentation — peer
selection and quorum are unchanged.

- io-metrics: per-peer PeerHealthState { online, consecutive_failures } registry
  plus record_peer_reachable/record_peer_unreachable. A peer flips offline after
  N consecutive failures (dial failures or RPC-triggered evictions) and back
  online on the next successful dial; the count of offline peers is published to
  the rustfs_cluster_servers_offline_total gauge.
- config: RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD (default 3, clamped >= 1).
- protos: build_channel marks the peer reachable on a successful dial and
  unreachable on a dial failure; evict_failed_connection feeds the failure signal
  too. Keyed by the real peer address, so control and bulk channels to one peer
  share health state.

Deferred (documented in docs/grpc-optimization P3): startup prewarm (no clean
topology-ready hook yet), the offline fast-bypass in peer routing (consistency-
sensitive; must not change quorum), and idempotent-read-only retry. This commit
is observability only.

Verification: cargo check/test on io-metrics, config, protos (new peer-health
state-machine and threshold-clamp tests); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3 control-channel prewarm + self-healing offline bypass

Add the remaining P3 connection-lifecycle levers (grpc-optimization G6/G8), both
env-gated and default-off so the base build is unchanged.

Prewarm (RUSTFS_INTERNODE_PREWARM, default off): RemoteDisk::new spawns a
best-effort background dial of the peer's control channel, deduped per peer
address, moving the connect cost off the first RPC. Failures fall through to the
existing lazy connect + recovery monitor.

Offline bypass (RUSTFS_INTERNODE_OFFLINE_BYPASS, default off): remote_disk
get_client/get_bulk_client fast-fail a peer already marked offline instead of
paying the connect timeout, so the erasure layer proceeds on quorum sooner. This
does NOT change quorum. It is self-healing: cluster_peer_should_bypass lets one
request per RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS (default 5s) through to recover
the peer even with no background monitor, and the recovery monitor's own probe
path calls the client directly so it is never bypassed.

io-metrics gains cluster_peer_is_offline / cluster_peer_should_bypass (with a
per-peer re-probe timestamp). Scope: data path only — remote_locker (lock RPCs,
most consistency-sensitive) is left dual-writing/unbypassed as a follow-up.

Verification: cargo check/test on io-metrics, config, ecstore (new self-healing
bypass tests; all 105 rpc tests green); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(internode): add A/B benchmark runbook for gRPC optimization stages

Reproducible before/after collection procedure for grpc-optimization P0–P3.
Since every stage is env-gated, before/after is the same binary with different
env — no rebuild. Documents, per stage: the exact env toggles (baseline vs
enabled column), which existing bench script to run
(run_internode_transport_baseline.sh / run_four_node_cluster_failover_bench.sh),
the Prometheus metrics to capture, and the acceptance gates from the design docs
(e.g. lock p99 down >= 20% for P1, msgpack fallback counter = 0 before enabling
P2, correct rustfs_cluster_servers_offline_total for P3).

Live runs require a multi-node cluster + load tool + Prometheus scrape and cannot
be produced in a single-process sandbox; artifacts land under target/bench
(gitignored) and attach to the PR.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3-2 lock-path offline bypass + P3-3 idempotent read retry

Extend the offline bypass to the lock path and add opt-in retries for idempotent
reads (grpc-optimization P3-2/P3-3). Both env-gated and default-off/zero.

Offline bypass (lock path): factor the bypass decision into a shared pub(crate)
internode_offline_bypass_reason(addr) and call it from remote_locker::get_client
too, so lock RPCs to an offline peer fast-fail (letting dsync reach quorum
sooner) instead of paying the connect timeout. Does not change quorum; the
self-healing re-probe keeps peers recoverable. Gated by
RUSTFS_INTERNODE_OFFLINE_BYPASS (default off).

Idempotent read retry (P3-3): add execute_read_with_retry — a bounded,
exponential-backoff retry for read-only/reentrant RPCs on transient network
errors — and route disk_info through it. RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES
defaults to 0 (disabled). Write/lock RPCs are never retried (quorum/idempotency
safety, per CLAUDE.md); the wrapper requires an Fn closure so only reads that
rebuild their request from borrowed inputs qualify.

Deferred: grpc.health.v1 (optional ecosystem-compat only; needs a new
tonic-health dep and 3-way hybrid-service wiring — internal needs are met by the
existing Ping RPC).

Verification: cargo check/test on config, ecstore (105 rpc tests green incl.
disk_info now via the retry wrapper); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(scripts): one-click internode gRPC A/B benchmark driver

Wrap the per-stage env matrix from the benchmark runbook into
scripts/run_internode_grpc_ab_bench.sh: given --stage <p0|p1|p2|p3> and --phase
<before|after>, it emits the stage/phase RUSTFS_INTERNODE_* server env to
<out-dir>/server-env.sh and runs the right underlying bench
(run_internode_transport_baseline.sh for p0/p1/p2, run_four_node_cluster_failover_bench.sh
for p3) into a labeled target/bench/internode-transport/<stage>-<phase>/.

Passthrough args after `--` reach the underlying bench; --dry-run previews the
env + command. The script is explicit that RUSTFS_INTERNODE_* are server env, so
for the load-driven stages the operator must restart rustfs with the emitted env
before the run; the docker four-node (p3) path exports them for a forwarding
compose. shellcheck-clean.

Runbook updated with a "One-click driver" section.

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(compose): forward RUSTFS_INTERNODE_* into the four-node cluster

The four-node local-build compose only forwarded a fixed whitelist of env, so
the internode gRPC knobs (grpc-optimization P0-P3) never reached the containers
and the A/B bench driver's "after" phase was a no-op. Forward the full
RUSTFS_INTERNODE_* set with defaults matching the binary defaults, so leaving
them unset is a no-op and the A/B driver can toggle a stage per phase.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(internode): address Copilot review — retry health action + poison-safe peer health

Two review nits on #4337:

- P3-3 idempotent read retry (remote_disk.rs): execute_read_with_retry ran every
  attempt through execute_with_timeout_for_op, which hardcodes
  FailureHealthAction::MarkFailure. So the first transient error could flip the
  disk faulty and short-circuit the remaining retries, and each attempt
  over-counted the failure. Route all but the final attempt through
  execute_with_timeout_for_op_and_health_action with IgnoreFailure; only the last
  attempt marks faulty/evicts. No default impact (retries default 0).

- Peer-health helpers (io-metrics): record_peer_reachable/record_peer_unreachable,
  cluster_peer_is_offline and cluster_peer_should_bypass early-returned on a
  poisoned mutex, permanently stalling the offline gauge and bypass state after a
  single panic. Recover via PoisonError::into_inner().

Verification: cargo check/test on io-metrics + ecstore (105 rpc tests green);
clippy clean on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 05:18:31 +08:00
Zhengchao An 0271abc14d docs: add MinIO compatibility router + file-format docs (#4328)
Add two durable architecture references grounded in current code:

- minio-rustfs-router-compatibility.md: MinIO cmd/api-router.go (S3
  object/bucket) and cmd/admin-router.go (admin /v3, /v4) vs RustFS
  implementation status, with per-row landing points and a gaps-only
  checklist of still-missing admin endpoints (profiling, healthinfo,
  LDAP IDP config, replication diff, MRF, batch, locks, speedtest,
  log stream, top, trace).
- minio-file-format-compat.md: xl.meta (meta_ver 3 write, <=3 read,
  XL2 magic, rs-vandermonde, HighwayHash256 bitrot, inline data) and
  .metadata.bin bucket-metadata interop matrix for the backlog#580
  items, plus old-RustFS -> new migration and a phased plan.

Cross-links s3-compatibility-matrix.md and admin-route-action-snapshot.md
and indexes both docs in docs/architecture/README.md.

Refs rustfs/backlog#596 rustfs/backlog#603 rustfs/backlog#580
2026-07-06 23:34:31 +08:00
Henry Guo be52e35a1f test(table-catalog): add vendor compatibility audit (#4299)
* test(table-catalog): add vendor compatibility audit

* fix(table-catalog): include OSS data-plane endpoint

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-06 14:12:19 +08:00
Henry Guo 5f2359de45 test(table-catalog): expand live conformance guide (#4277)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-05 15:41:08 +08:00
houseme db277b17a4 fix: harden GET object performance paths (#4271)
* fix: harden GET object performance paths

* fix: satisfy GET multipart layer guard

* fix: keep v1 list markers S3 compatible

* perf: tighten GET direct-memory decision

* ci: isolate s3tests from scanner workload

* refactor: simplify get object body lifecycle

* fix: satisfy get object clippy
2026-07-05 12:03:22 +08:00
Zhengchao An 0271d7aa2b refactor(ecstore): narrow api facade exports (#4270)
* refactor(ecstore): narrow bucket api facade

* refactor(ecstore): narrow more api facades

* test(ecstore): stabilize multipart cleanup test
2026-07-05 06:06:39 +08:00
Zhengchao An a6b3e4f5d6 refactor: use canonical Rust module paths (#4269) 2026-07-05 03:01:14 +08:00
Henry Guo be45b35472 feat(table-catalog): add distributed maintenance scheduling (#4257)
* feat(table-catalog): add distributed maintenance scheduling

* fix(table-catalog): address scheduler review feedback

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-07-04 23:44:08 +08:00
Zhengchao An d0792b87be refactor(lifecycle): extract core rule contracts (#4258) 2026-07-04 20:05:56 +08:00
Zhengchao An 123552d32b refactor(replication): own utility wire contracts (#4255) 2026-07-04 08:00:37 +08:00
Zhengchao An 3d4fb532e5 refactor(replication): own filemeta wire contracts (#4254) 2026-07-04 06:39:54 +08:00
Zhengchao An 125c228832 refactor(replication): own delete DTO contracts (#4253) 2026-07-04 04:00:27 +08:00
Zhengchao An 8a0617865b refactor(replication): route app storage through ecstore (#4251) 2026-07-04 02:00:28 +08:00
Henry Guo 2214f67fba feat(table-catalog): deepen row-level maintenance planning (#4249)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-03 23:49:40 +08:00
Zhengchao An 4883d3eb50 refactor(replication): route runtime facades through ecstore (#4248) 2026-07-03 23:49:24 +08:00
Zhengchao An 7cc470cb08 refactor(replication): isolate ecstore boundary imports (#4247) 2026-07-03 23:18:27 +08:00
Zhengchao An d19ee6c51c refactor(replication): isolate storage api contracts (#4244) 2026-07-03 22:48:21 +08:00
Zhengchao An 24e88a2cf4 refactor(replication): isolate filemeta facade contracts (#4243) 2026-07-03 22:20:46 +08:00
Zhengchao An 6839e57c96 refactor(replication): isolate storage api contracts (#4240)
* refactor(replication): isolate storage api contracts

* docs(replication): record storage api contract boundary
2026-07-03 22:10:16 +08:00
Henry Guo 782e73ca92 feat(table-catalog): add maintenance audit timeline (#4207)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-03 00:36:17 +08:00
Henry Guo 31c026dd4f feat(table-catalog): add maintenance quarantine operations (#4201)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 23:30:44 +08:00
Zhengchao An d1db9a10cd chore(docs): refresh agent docs, guard doc paths, archive plans (#4203)
Agent-instruction and architecture docs had drifted from the code:

- CLAUDE.md: slim to commands + pointers; fix wrong claim that
  `make pre-commit` is the full pre-PR gate (that is `make pre-pr`);
  drop stale pre-#3929 file paths and merged bug narratives
- AGENTS.md: drop dead `rust-refactor-helper` skill rule and the
  hand-maintained (already stale) scoped-AGENTS index; link
  architecture docs from Sources of Truth
- .github/AGENTS.md: replace the outdated copied CI command matrix
  with a pointer to ci.yml
- crates/AGENTS.md: merge duplicated Testing sections
- ARCHITECTURE.md: resolve the utils->config contradiction (edges are
  removed), mark volatile counts as snapshots, fix a bad path
- docs/architecture: add README router; move one-shot plans/trackers
  (rebalance-decommission phases, migration-progress ledger, PR
  template) to docs/superpowers/plans with archive headers; fix stale
  source paths in kept inventories (core/sets.rs, core/pools.rs,
  store/mod.rs, startup_* split from #3671)
- docs/operations/tier-ilm-debugging.md: extracted tier debugging
  playbook with corrected paths
- scripts/check_doc_paths.sh: new guard failing pre-commit/pre-pr when
  instruction/architecture docs reference nonexistent file paths
- .claude/skills: add tier-debug and arch-checks repo skills;
  .gitignore now keeps .claude/skills and docs/operations committable

Verification: ./scripts/check_doc_paths.sh,
./scripts/check_architecture_migration_rules.sh (both pass)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:30:13 +08:00
Henry Guo 962c20bf47 feat(table-catalog): deepen maintenance rewrite support (#4192)
* feat(table-catalog): deepen maintenance rewrite support

* fix(ecstore): satisfy replication boundary clippy lint

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 22:35:07 +08:00
Henry Guo 14c0fca576 test(table-catalog): extend live engine conformance probes (#4184)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 16:34:37 +08:00
Zhengchao An e80391f1aa refactor(replication): extract resync contracts crate (#4154) 2026-07-02 01:30:01 +08:00
Zhengchao An 00e93f8267 fix(storage): tighten replication handle visibility (#4153) 2026-07-02 00:57:08 +08:00
Zhengchao An 572a001f93 refactor(storage): hide replication runtime handles (#4152) 2026-07-02 00:40:42 +08:00
Zhengchao An 14016dbe8c refactor(scanner): hide replication queue DTOs (#4150) 2026-07-02 00:17:03 +08:00
Zhengchao An 644a472e52 refactor(obs): hide replication stats handle (#4149) 2026-07-01 23:37:23 +08:00
Zhengchao An b43655ccdd refactor(app): hide replication object DTOs (#4147) 2026-07-01 23:02:00 +08:00
Henry Guo a305160087 feat(table-catalog): add maintenance scheduler guardrails (#4123) 2026-07-01 22:31:19 +08:00
Zhengchao An b94e7d514d refactor(admin): hide replication resync DTOs (#4146) 2026-07-01 22:18:06 +08:00
Zhengchao An 22cbd41ee9 refactor(ecstore): add replication owner bridges (#4141)
* refactor(ecstore): add replication owner bridges

* fix(ecstore): tighten replication owner guards
2026-07-01 21:51:26 +08:00
Zhengchao An c0dfc260ca refactor(ecstore): add replication object bridge (#4140) 2026-07-01 19:24:20 +08:00
Zhengchao An e5ab6ee8e4 refactor(ecstore): make replication facade explicit (#4139) 2026-07-01 18:54:05 +08:00
Zhengchao An fc806679fb refactor(ecstore): add replication work bridges (#4137) 2026-07-01 18:37:01 +08:00
Zhengchao An 28e87c1cab refactor(ecstore): name replication boundary contracts (#4134) 2026-07-01 17:55:12 +08:00
Zhengchao An b0f491549a refactor(ecstore): hide replication runtime types (#4131) 2026-07-01 15:39:02 +08:00
Zhengchao An 8fd532ebb7 refactor(ecstore): use local replication paths (#4130) 2026-07-01 15:08:26 +08:00
Zhengchao An 7cb42529fc refactor(ecstore): route replication config access (#4128) 2026-07-01 09:30:25 +08:00
Zhengchao An 722c0805ce refactor(ecstore): route replication errors (#4127) 2026-07-01 08:13:57 +08:00
Zhengchao An 50a3ed8bf3 refactor(ecstore): route replication event host (#4126) 2026-07-01 06:00:50 +08:00
Zhengchao An e917f1f4be refactor(ecstore): route replication filemeta contracts (#4122) 2026-07-01 01:01:01 +08:00
Zhengchao An 942a8b505d refactor(ecstore): route replication metadata paths (#4121) 2026-06-30 22:48:34 +08:00
Henry Guo 5e68455eed test(table-catalog): add disaster recovery rehearsal probes (#4119)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-30 21:27:06 +08:00
Zhengchao An da7f421f69 refactor(ecstore): route replication storage contracts (#4118) 2026-06-30 20:12:06 +08:00
Henry Guo cfb24b31ea test(table-catalog): add live client conformance harness (#4110)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-30 16:33:14 +08:00
Zhengchao An 70a1f37508 refactor(ecstore): route replication target types (#4114) 2026-06-30 16:29:05 +08:00