docs(m12): refresh API, specs, ops, and roadmap to the shipped M12 reality

- API.md: document `similar_to`/`region`/`unavailable_shards` on /feed and
  /search, the new POST /vector_search k-NN probe, and the cluster-node-only
  routes (/cluster/*, /sharded/*, /hardnegs)
- CHANGELOG.md: M12 entries — multi-vector preference + ANN candidate-gen,
  idle-readiness + TLS scale-up (m12p5/p6), sharded ingestion (m12p4)
- ROADMAP.md: mark M11 + M12 COMPLETE; restate the v1.0 bar (30-day-green
  nightly calendar + Ref-A/k3s throughput re-runs)
- prometheus-alerts.yaml: add ship-stall, quorum-lag, divergence-quarantine,
  reseed-pending, and snapshot-pin-force-drop cluster alerts
- check-docs.sh: self-updating milestone-status freshness guard derived from
  ROADMAP's latest COMPLETE milestone
- refresh specs (00-14), ai-lookup, guides, and runbooks to M0-M12
This commit is contained in:
jx12n 2026-06-23 21:39:55 -06:00
parent 6a937fc4bc
commit 4051077cff
29 changed files with 261 additions and 36 deletions

100
API.md
View File

@ -970,10 +970,13 @@ Retrieve a ranked feed.
|---|---|---|---| |---|---|---|---|
| `user_id` | `u64` | — | User for personalization (optional) | | `user_id` | `u64` | — | User for personalization (optional) |
| `profile` | `string` | `"for_you"` | Ranking profile name | | `profile` | `string` | `"for_you"` | Ranking profile name |
| `limit` | `u32` | `20` | Max results | | `limit` | `u32` | `20` | Max results (clamped to 1000) |
| `similar_to` | `u64` | — | Seed item for "more like this": with `profile=related`, sources candidates by ANN nearest-neighbor over this item's embedding |
| `region` | `string` | — | Target region (cluster mode only; rejected with `400` in standalone) |
``` ```
GET /feed?user_id=123&profile=trending&limit=25 GET /feed?user_id=123&profile=trending&limit=25
GET /feed?profile=related&similar_to=42&limit=20
``` ```
**Response:** **Response:**
@ -991,11 +994,18 @@ GET /feed?user_id=123&profile=trending&limit=25
] ]
} }
], ],
"total_candidates": 1000 "total_candidates": 1000,
"region": null,
"unavailable_shards": ["group-2"]
} }
``` ```
The `signals` field is omitted when empty. The `signals` field is omitted when empty. `region` is the region the feed was
served from in cluster mode (`null` standalone). `unavailable_shards` is present
**only** when a cross-shard read was degraded — it lists the shard groups that
could not be reached, so a partial page (fewer items, lower `total_candidates`)
is never silently indistinguishable from a complete one. It is omitted on a
complete read.
#### `GET /search` #### `GET /search`
@ -1005,7 +1015,8 @@ Text search with optional personalization.
|---|---|---|---| |---|---|---|---|
| `query` | `string` | — | Search text (**required**) | | `query` | `string` | — | Search text (**required**) |
| `user_id` | `u64` | — | User for personalization (optional) | | `user_id` | `u64` | — | User for personalization (optional) |
| `limit` | `u32` | `20` | Max results | | `limit` | `u32` | `20` | Max results (clamped to 1000) |
| `region` | `string` | — | Target region (cluster mode only; rejected with `400` in standalone) |
``` ```
GET /search?query=jazz+piano&user_id=123&limit=10 GET /search?query=jazz+piano&user_id=123&limit=10
@ -1024,11 +1035,59 @@ GET /search?query=jazz+piano&user_id=123&limit=10
"semantic_score": 0.92 "semantic_score": 0.92
} }
], ],
"total_candidates": 50 "total_candidates": 50,
"region": null,
"unavailable_shards": ["group-2"]
} }
``` ```
`bm25_score` and `semantic_score` are omitted when not applicable. `bm25_score` and `semantic_score` are omitted when not applicable. `region` and
`unavailable_shards` behave exactly as on [`GET /feed`](#get-feed): `region` is
the serving region in cluster mode (`null` standalone), and `unavailable_shards`
is present only when a cross-shard read was degraded (partial page) and omitted
on a complete read.
#### `POST /vector_search`
Pure k-NN ANN probe over the item content vector slot — **no** profile scoring,
fusion, or diversity. This is the recall-measurement / raw nearest-neighbor
surface (used by the `tidal-stress --verify-recall` harness).
**Request body:**
```json
{
"vector": [0.013, -0.41, 0.22],
"k": 10,
"ef_search": 200
}
```
| Field | Type | Default | Description |
|---|---|---|---|
| `vector` | `f32[]` | — | Dense query vector; must match the item content slot's dimensionality (**required**) |
| `k` | `u32` | `10` | Number of nearest neighbors to return (clamped to 1000) |
| `ef_search` | `u32` | — | Optional per-request HNSW beam-width override (the recall/latency knob); omitted = the slot's configured default |
**Response:**
```json
{
"items": [
{"entity_id": 42, "distance": 0.018},
{"entity_id": 17, "distance": 0.041}
],
"region": null,
"unavailable_shards": ["group-2"]
}
```
Each match carries `entity_id` and `distance` (L2-squared distance from the query
vector, lower = more similar; for the L2-normalized vectors tidalDB stores this
lies in `[0.0, 4.0]` and is monotonic with cosine distance), ordered closest-first.
`region` is always `null` here (the probe serves locally — standalone, or merged
across a node's hosted shard groups in cluster mode). `unavailable_shards` is
present only when a cross-shard probe was degraded (partial nearest-set).
### Health Endpoints ### Health Endpoints
@ -1055,6 +1114,35 @@ The cluster server serves a superset document that also includes the
`/cluster/*` and `/sharded/*` routes. See `/cluster/*` and `/sharded/*` routes. See
[docs/guides/server-deployment.md](docs/guides/server-deployment.md). [docs/guides/server-deployment.md](docs/guides/server-deployment.md).
### Cluster Endpoints
These routes exist **only on cluster (region) nodes** — they are absent from the
standalone server. Status routes are read-only; the rebalance verbs require an
admin API key. Full operational detail is in
[docs/guides/server-deployment.md](docs/guides/server-deployment.md) and
[docs/runbooks/kubernetes.md](docs/runbooks/kubernetes.md).
| Method | Path | Purpose |
|---|---|---|
| `GET` | `/cluster/status` | Aggregated cluster view — leader, relay high-water-mark, per-region lag, and a per-shard-group `shards` array |
| `GET` | `/cluster/status/local` | This node's local view — its hosted shard groups and which leaderships it holds |
| `POST` | `/cluster/shards/{id}/transfer` | Transfer shard group `{id}`'s leadership to a named replica (fenced promote; body `{"region": "<name>"}`) |
| `POST` | `/cluster/shards/{id}/replicas` | Add or remove a replica of shard group `{id}` (body `{"action": "add"\|"remove", "name": ..., "grpc_addr": ..., "http_addr": ...}`; addrs required for `add`) |
The cluster node also exposes the membership/recovery verbs `POST /cluster/join`,
`/cluster/promote`, `/cluster/members{,/remove}`, `/cluster/partition`, `/cluster/heal`,
`/cluster/catchup`, `/cluster/reseed`, and `/cluster/reconcile`, plus the sharded
data surface `POST /sharded/{items,embeddings,signals}` and `GET /sharded/{feed,search}`.
### Cluster-Node-Only Data Endpoints
| Method | Path | Purpose |
|---|---|---|
| `POST` | `/hardnegs` | Record a hide hard-negative for `(user, item)` (body `{"user_id": ..., "item_id": ...}`); `204 No Content`. Cluster-node-only — converges via the `/cluster/reconcile` LWW snapshot |
> `POST /vector_search` (documented above) is served by **both** the standalone
> and cluster servers.
**`GET /health` response:** **`GET /health` response:**
```json ```json

View File

@ -255,7 +255,7 @@ This matches how ScyllaDB uses USearch in production and how Weaviate and Qdrant
User interest is not a single vector. Averaging engagement embeddings across topics ("hiking," "cooking," "cars") produces a centroid that represents none of them (PinnerSage, KDD 2020). Instead, each user's preference is represented as up to `K_MAX=10` interest cluster centroids, maintained online by the database as signals arrive. User interest is not a single vector. Averaging engagement embeddings across topics ("hiking," "cooking," "cars") produces a centroid that represents none of them (PinnerSage, KDD 2020). Instead, each user's preference is represented as up to `K_MAX=10` interest cluster centroids, maintained online by the database as signals arrive.
**As built** (`entities/multi_preference.rs`, design in `docs/research/multi-vector-preference.md`): clusters are maintained by **online sequential k-means with a DP-means threshold split** — a new interaction either updates its nearest cluster (per-cluster adaptive EMA, `alpha = base / (1 + ln(count + 1))`) or, if it exceeds the split threshold and `K_MAX` is not reached, opens a new cluster; at the cap the nearest cluster absorbs it (no eviction). Per-cluster *importance* composes the canonical forward-decay kernel (`signals/decay.rs`), anchored to each engagement's event timestamp, so stale interests fade. At query time the `for_you` path selects the top-`M` clusters by current importance, issues `M` ANN queries **sequentially** (`candidate_gen::ann_candidates_multi`; the loop is parallelizable but not yet parallelized), and merges by **best (min) distance** — not by score; the personalization boost is the max cosine over **all** the user's clusters. Stage-3 re-ranking is unchanged. Users below `COLD_START_N=5` interactions fall back to a **single** adaptive-LR vector (`entities/preference.rs`) — the documented cold-start tier. **As built** (`entities/multi_preference.rs`, design in `docs/research/multi-vector-preference.md`): clusters are maintained by **online sequential k-means with a DP-means threshold split** — a new interaction either updates its nearest cluster (per-cluster adaptive EMA, `alpha = base / (1 + ln(count + 1))`) or, if it exceeds the split threshold and `K_MAX` is not reached, opens a new cluster; at the cap the nearest cluster absorbs it (no eviction). Per-cluster *importance* composes the canonical forward-decay kernel (`signals/decay.rs`), anchored to each engagement's event timestamp, so stale interests fade. At query time the `for_you` path selects the top-`M` clusters by current importance, issues `M` ANN queries **sequentially** by design (`candidate_gen::ann_candidates_multi`; parallelizable — that optimization is deferred), and merges by **best (min) distance** — not by score; the personalization boost is the max cosine over **all** the user's clusters. Stage-3 re-ranking is unchanged. Users below `COLD_START_N=5` interactions fall back to a **single** adaptive-LR vector (`entities/preference.rs`) — the documented cold-start tier.
**Aspirational:** PinnerSage *proper* uses **medoids** (actual item embeddings, not maintained centroids) computed by **offline Ward hierarchical clustering**. That batch tier is disqualified for an embeddable single-node DB today. The shipped `Tag::Preference` per-cluster row already carries the `anchor_ts` / `importance_at_anchor` decay state a periodic in-process medoid recluster would reset, so *that* field-level change needs no migration; the recluster's other input — a bounded per-user interaction-embedding window — lands as an additive `Tag::PreferenceWindow` row (reserved in `storage/keys.rs`, not yet populated), never a rewrite of existing rows. **Aspirational:** PinnerSage *proper* uses **medoids** (actual item embeddings, not maintained centroids) computed by **offline Ward hierarchical clustering**. That batch tier is disqualified for an embeddable single-node DB today. The shipped `Tag::Preference` per-cluster row already carries the `anchor_ts` / `importance_at_anchor` decay state a periodic in-process medoid recluster would reset, so *that* field-level change needs no migration; the recluster's other input — a bounded per-user interaction-embedding window — lands as an additive `Tag::PreferenceWindow` row (reserved in `storage/keys.rs`, not yet populated), never a rewrite of existing rows.

View File

@ -6,6 +6,45 @@ All notable changes to tidalDB will be documented in this file.
### Added ### Added
**Multi-vector user preference modeling + ANN candidate-gen (M12) — a warm user is many interests, not one averaged vector: per-user preference clusters drive a top-M ANN fan-out in `for_you`**
- **Online preference clustering (`entities/multi_preference.rs`).** A warm user
(≥ `COLD_START_N = 5` interactions) maintains up to `K_MAX` preference clusters
built by online sequential k-means with a DP-means threshold split: a new
engagement updates its nearest cluster (per-cluster adaptive EMA) or, past the
split threshold and under the cap, opens a new cluster; at the cap the nearest
cluster absorbs it. Per-cluster *importance* composes the canonical forward-decay
kernel anchored to each engagement's timestamp, so stale interests fade.
- **Top-M ANN fan-out.** At query time `for_you` selects the top-`M` clusters by
current importance, issues `M` ANN queries (`candidate_gen::ann_candidates_multi`),
and merges by best (min) distance; the personalization boost is the max cosine
over all clusters. Users below the cold-start threshold keep the single
adaptive-LR vector (`entities/preference.rs`). Design: `docs/research/multi-vector-preference.md`.
**Idle-readiness + TLS scale-up (m12p5m12p6) — followers converge readiness on an idle cluster, and elasticity is proven over REAL mTLS on k8s**
- **Idle-readiness convergence (m12p5).** The leader heartbeat now carries its live
frontier (`leader_last_seq`), so a caught-up follower flips `/health` Ready on an
idle cluster instead of stalling until the next status poll or ship. Cert SAN
wildcard widened for scale-to-5.
- **TLS scale-up (m12p6).** A real `kubectl scale 3→5` exercised seed-join over
mTLS on k8s (kind) for the first time: joiners flip Ready in ~13s via the
idle-readiness heartbeat, auto-promote to Voter, reach full content parity at
lag 0, with zero acked DATA loss across scale-down. Fixes span a six-bug chain —
`https://` seed scheme, rustls `CryptoProvider` install order, headless seed
Service, cold-handshake poll timeout, two-tier cert-manager PKI, and the
`grpc_tls_for` CA fallback for a not-yet-in-topology joiner. Persists the HNSW
graph and skips a suspect graph on reseed-pending close.
**Sharded ingestion (m12p4) — scatter-gather across shard groups with cross-shard unified reads**
- **Scatter-gather pool + cross-shard reads.** Writes hash-route across a 3-group
`shards:` topology; reads unify across groups (L4). Ran REAL on kind with a
2-generator load job. Fixed an HTTP/2 204 forward-relay bug (a synthesized JSON
body on a 204 relay triggered an h2 `RST_STREAM`). Confirmed with data: at fixed
per-pod CPU, full-placement sharding scales failover, not write throughput;
the ≥2.5×-and-≥5,000/s scaling target remains Ref-A/k3s-pending.
**Index tuning + recall/memory at the production shape (m12p3) — the G2 work: per-query `ef_search` is now honored, the brute-force crossover scales with dimensionality, and the HNSW recall/latency/memory frontier is measured at 1536-D with a real exact oracle** **Index tuning + recall/memory at the production shape (m12p3) — the G2 work: per-query `ef_search` is now honored, the brute-force crossover scales with dimensionality, and the HNSW recall/latency/memory frontier is measured at 1536-D with a real exact oracle**
- **Per-query `ef_search` override — now real.** `UsearchIndex::search` / - **Per-query `ef_search` override — now real.** `UsearchIndex::search` /

View File

@ -5,7 +5,7 @@
A single-node-first, embeddable Rust database for the **personalized content ranking problem**. Replaces the 6-system stack (Elasticsearch + Redis + Kafka + feature store + vector DB + ranking service) with a single process, single query interface, and single operational model. A single-node-first, embeddable Rust database for the **personalized content ranking problem**. Replaces the 6-system stack (Elasticsearch + Redis + Kafka + feature store + vector DB + ranking service) with a single process, single query interface, and single operational model.
**Status:** Implemented — M0M10 shipped (embeddable engine + multi-region cluster mode). This repository is a standalone Cargo workspace: the engine is the `tidaldb` crate at `tidal/`, with `tidal-net/`, `tidal-server/`, and `tidalctl/` as workspace siblings and example consumers under `applications/`. Pre-1.0 — APIs are stable for shipped features, but breaking changes are possible before 1.0. See [CHANGELOG.md](CHANGELOG.md) for milestone history and [docs/planning/ROADMAP.md](docs/planning/ROADMAP.md) for status and known gaps. **Status:** Implemented — M0M12 shipped (embeddable engine + multi-region cluster mode). M11 (Enterprise-Grade Cluster) closed all nine phases 2026-06-13; M12 (Vector Retrieval at production shape) shipped the recall/ANN/index-tuning G1/G2 work, sharded ingestion, cluster elasticity (idle-readiness, TLS scale-up), and multi-vector preference modeling. This repository is a standalone Cargo workspace: the engine is the `tidaldb` crate at `tidal/`, with `tidal-net/`, `tidal-server/`, and `tidalctl/` as workspace siblings and example consumers under `applications/`. Pre-1.0 — APIs are stable for shipped features, but breaking changes are possible before 1.0. See [CHANGELOG.md](CHANGELOG.md) for milestone history and [docs/planning/ROADMAP.md](docs/planning/ROADMAP.md) for status and known gaps.
## Find Your Guide ## Find Your Guide

View File

@ -300,7 +300,8 @@ Milestones completed:
- Storage engine, WAL, entity store, signal ledger - Storage engine, WAL, entity store, signal ledger
- RETRIEVE query: candidate retrieval, filtering, scoring, diversity, pagination - RETRIEVE query: candidate retrieval, filtering, scoring, diversity, pagination
- Vector index (USearch HNSW) with adaptive filtered search - Vector index (USearch HNSW) with adaptive filtered search; ANN candidate generation in RETRIEVE with honored per-query `ef_search`
- Multi-vector user preference modeling (per-user interest clusters with decayed importance)
- 25 built-in ranking profiles - 25 built-in ranking profiles
- BM25 full-text search (Tantivy) + hybrid RRF fusion - BM25 full-text search (Tantivy) + hybrid RRF fusion
- Creator search and creator profiles - Creator search and creator profiles

View File

@ -13,6 +13,7 @@ The query interface is a single operation that encapsulates candidate retrieval,
- SEARCH: keyword + semantic + hybrid retrieval - SEARCH: keyword + semantic + hybrid retrieval
- SIGNAL: engagement event write-back (closes the feedback loop in the same transaction) - SIGNAL: engagement event write-back (closes the feedback loop in the same transaction)
- All queries accept: FOR USER, USING PROFILE, FILTER, DIVERSITY, LIMIT - All queries accept: FOR USER, USING PROFILE, FILTER, DIVERSITY, LIMIT
- RETRIEVE also accepts SIMILAR TO (`similar_to` — a seed item for "more like this") and an optional `ef_search` ANN-recall/latency override
- Filters are composable — any combination is valid - Filters are composable — any combination is valid
**File Pointer:** `VISION.md:47-57` **File Pointer:** `VISION.md:47-57`
@ -56,6 +57,7 @@ SIMILAR TO @item_id
FOR USER @user_id FOR USER @user_id
USING PROFILE related USING PROFILE related
FILTER unseen FILTER unseen
EF_SEARCH 128 # optional: tune ANN recall vs. latency per query
LIMIT 10 LIMIT 10
``` ```

View File

@ -10,6 +10,7 @@ Entities are the nodes of the system. Three types: Items (content), Users, and C
**Key Facts:** **Key Facts:**
- Items have metadata, embeddings, and signals — signals are typed timestamped streams, not fields - Items have metadata, embeddings, and signals — signals are typed timestamped streams, not fields
- Users have preferences, histories, and relationships — living profiles that update continuously - Users have preferences, histories, and relationships — living profiles that update continuously
- A user's taste is **two-tier**: cold-start users (`< 5` interactions) carry a single adaptive-LR **preference vector** (K=1); warm users carry **multiple preference clusters** (one centroid per coherent interest)
- Creators are linked to Items and have their own embeddings (aggregated from catalog) - Creators are linked to Items and have their own embeddings (aggregated from catalog)
- Relationships are first-class edges between entities (weighted, directional, traversable) - Relationships are first-class edges between entities (weighted, directional, traversable)
@ -19,7 +20,12 @@ Entities are the nodes of the system. Three types: Items (content), Users, and C
Items enter via the WRITE path with metadata + embedding. A signal ledger is initialized at zero. Cold start exploration budget is applied automatically. Items are immediately queryable after commit. Items enter via the WRITE path with metadata + embedding. A signal ledger is initialized at zero. Cold start exploration budget is applied automatically. Items are immediately queryable after commit.
Users accumulate implicit preference vectors from engagement history. Preference vectors update on every signal write (like, skip, hide, completion). Users accumulate implicit taste from engagement history, updated on every positive-engagement signal write (like, completion, etc.). The model has two tiers, gated on the user's total positive interaction count (`COLD_START_N = 5`):
- **Cold start (`< 5` interactions):** a single **preference vector** (`entities/preference.rs`) blended via an adaptive learning rate (`alpha = base / (1 + ln(1 + count))`) and L2-normalized — the K=1 case.
- **Warm (≥ 5 interactions):** multiple **preference clusters** (`entities/multi_preference.rs`). Each positive engagement is assigned to its nearest centroid by cosine; a DP-means threshold (τ, default `0.55`) opens a new cluster when nothing is similar enough, capped at `K_MAX = 10` (over the cap, the engagement is assigned to the nearest centroid, never evicted). Each cluster carries its own adaptive LR and a forward-decayed importance (default half-life 30 days). On crossing the threshold the single vector seeds cluster 0, so the cold taste is never discarded.
This keeps a user who engages with hiking, cooking, and cars from collapsing into one averaged centroid that represents none of them.
Creators are entities with their own embeddings derived from their item catalog. Creator-level signals include engagement rate, posting frequency, and follower count. Creators are entities with their own embeddings derived from their item catalog. Creator-level signals include engagement rate, posting frequency, and follower count.

View File

@ -11,7 +11,7 @@ A ranking profile is a named, versioned bundle that fully specifies how a query
- Profiles are schema-level declarations, not application code. The app names a profile; the database executes the whole pipeline. - Profiles are schema-level declarations, not application code. The app names a profile; the database executes the whole pipeline.
- The same profile operates over different candidate sets (global / category / social graph / cohort) depending on its candidate strategy and the query's filters. - The same profile operates over different candidate sets (global / category / social graph / cohort) depending on its candidate strategy and the query's filters.
- Personalization (`for_you`, `following`, `related`, `notification`, `date_saved`) requires `FOR USER` context — pass `.for_user(uid)` on the query. Without it the executor cannot read the user's preference vector / saved-state and either degrades to non-personalized scoring or, for `date_saved`, returns an error. - Personalization (`for_you`, `following`, `related`, `notification`, `date_saved`) requires `FOR USER` context — pass `.for_user(uid)` on the query. Without it the executor cannot read the user's preference vector / saved-state and either degrades to non-personalized scoring or, for `date_saved`, returns an error.
- **Preference-vector personalization only updates from signals declared `.positive_engagement(true)`.** `signal_with_context(..)` folds the item embedding into the user's taste vector only for positive-engagement signal types; negative signals (skip/hide/block) update seen-state and hard-negatives but do not pull the taste vector toward the item. Declare engagement signals accordingly or `for_you` will not learn. - **Preference personalization only updates from signals declared `.positive_engagement(true)`.** `signal_with_context(..)` folds the item embedding into the user's taste only for positive-engagement signal types; negative signals (skip/hide/block) update seen-state and hard-negatives but do not pull the taste toward the item. Declare engagement signals accordingly or `for_you` will not learn. For a **warm user** (≥ 5 interactions) the engagement is assigned to the **nearest preference cluster** (or opens a new one past the DP-means threshold); for a cold-start user it blends the single preference vector. At query time `for_you` fans out over the user's **top-M clusters by current decayed importance** (`DEFAULT_TOP_M ≈ 3`), issues one ANN query per cluster centroid, and merges by best (min) distance — see [Entities](./entities.md).
- Built-in defaults below are read straight from `tidal/src/ranking/builtins.rs`. Where a value is intentionally application-tuning territory, it is described qualitatively rather than invented. - Built-in defaults below are read straight from `tidal/src/ranking/builtins.rs`. Where a value is intentionally application-tuning territory, it is described qualitatively rather than invented.
**File Pointers:** `tidal/src/ranking/builtins.rs` (the 25 built-ins), `tidal/src/ranking/profile.rs` (`RankingProfile`, `DiversitySpec`, `Sort`, `Boost`, `CandidateStrategy`), `VISION.md:43-55`. **File Pointers:** `tidal/src/ranking/builtins.rs` (the 25 built-ins), `tidal/src/ranking/profile.rs` (`RankingProfile`, `DiversitySpec`, `Sort`, `Boost`, `CandidateStrategy`), `VISION.md:43-55`.
@ -51,6 +51,7 @@ A ranking profile is a named, versioned bundle that fully specifies how a query
### Notes on specific built-ins ### Notes on specific built-ins
- **`trending` / `cohort_trending` and the `view`/`share` boosts.** The `Sort::Trending` formula (`view_vel + 2·share_vel` over 24h) is the ordering authority on the global path and *replaces* the boost loop, so the executor skips boosts there (no double-count). The same `view`/`share` velocity boosts are not dead weight: the cohort rescore path (`rescore_with_cohort`) consumes `profile.boosts` directly to re-score against a cohort's ledger. Keep the boost weights mirroring the formula so cohort ordering matches global ordering. - **`trending` / `cohort_trending` and the `view`/`share` boosts.** The `Sort::Trending` formula (`view_vel + 2·share_vel` over 24h) is the ordering authority on the global path and *replaces* the boost loop, so the executor skips boosts there (no double-count). The same `view`/`share` velocity boosts are not dead weight: the cohort rescore path (`rescore_with_cohort`) consumes `profile.boosts` directly to re-score against a cohort's ledger. Keep the boost weights mirroring the formula so cohort ordering matches global ordering.
- **`SignalRanked` candidate generation is served from a cached per-signal-type top-K** (`signals/ledger/hot_top_k.rs`). Because the same decay `lambda` multiplies every entity of a signal type by the same factor, time decay never changes the relative order — so the cached top-K stays valid until the **next signal write**, not the next clock tick. The O(N) ledger rebuild is throttled (sub-millisecond and always fresh under 50k entries; at most one rebuild per second above that) to keep the read p99 off the scan path.
- **`for_you` boosts vs. preference match.** Beyond the `Hot { gravity = 1.5 }` recency curve, `for_you` boosts decayed `view` (weight 1.0), decayed `like` (weight 2.0), and 24h `share` velocity (weight 1.5), layered on top of the user-preference-vector match. The 10% exploration injection is what keeps it from collapsing into a filter bubble. - **`for_you` boosts vs. preference match.** Beyond the `Hot { gravity = 1.5 }` recency curve, `for_you` boosts decayed `view` (weight 1.0), decayed `like` (weight 2.0), and 24h `share` velocity (weight 1.5), layered on top of the user-preference-vector match. The 10% exploration injection is what keeps it from collapsing into a filter bubble.
- **`search` has `sort = None` on purpose.** The fused RRF (BM25 + ANN) score from the search executor is the primary ordering signal; the profile only adds a small `view`/`like` quality overlay (weights 0.5 / 0.8) so relevance stays in charge. Exploration is `0.0` so results are deterministic for a given query. Set diversity yourself via `SearchBuilder::diversity(..)`. - **`search` has `sort = None` on purpose.** The fused RRF (BM25 + ANN) score from the search executor is the primary ordering signal; the profile only adds a small `view`/`like` quality overlay (weights 0.5 / 0.8) so relevance stays in charge. Exploration is `0.0` so results are deterministic for a given query. Set diversity yourself via `SearchBuilder::diversity(..)`.
- **Three sort modes have no built-in profile shortcut.** `MostFollowed` and `CreatorEngagementRate` rank **creators** (not items) and need a creator-scoped candidate strategy plus a `follow` signal the generic schema does not guarantee; `Rising` (1h/24h view-velocity acceleration ratio) overlaps `trending`/`hot` and is sensitive to the exact window pair. All three are reachable via a custom schema profile (`SchemaBuilder::ranking_profile(..).sort(..)`); only the named-convenience built-in is omitted. - **Three sort modes have no built-in profile shortcut.** `MostFollowed` and `CreatorEngagementRate` rank **creators** (not items) and need a creator-scoped candidate strategy plus a `follow` signal the generic schema does not guarantee; `Rising` (1h/24h view-velocity acceleration ratio) overlaps `trending`/`hot` and is sensitive to the exact window pair. All three are reachable via a custom schema profile (`SchemaBuilder::ranking_profile(..).sort(..)`); only the named-convenience built-in is omitted.
@ -63,7 +64,7 @@ The candidate strategy decides *which* items the profile even considers before s
|----------|--------------|-----------------------| |----------|--------------|-----------------------|
| `Scan` (`sort_field`) | Population scan over the item keyspace; default for built-ins (`sort_field = "created_at"`) | most population + sort-coverage profiles | | `Scan` (`sort_field`) | Population scan over the item keyspace; default for built-ins (`sort_field = "created_at"`) | most population + sort-coverage profiles |
| `Relationship` | Sources candidates from the user's relationship graph (followed creators) | `following`, `notification` | | `Relationship` | Sources candidates from the user's relationship graph (followed creators) | `following`, `notification` |
| `Ann { slot, limit }` | k-NN over an embedding slot (set via the query's `.similar_to` / `Search`'s `.vector`) | custom; `related` uses the seed embedding via the query | | `Ann { slot, limit }` | k-NN over an embedding slot (set via the query's `.similar_to` / `Search`'s `.vector`); the per-query `ef_search` override (m12p3) trades recall for latency on this path | custom; `related` uses the seed embedding via the query, `for_you` over the user's preference clusters |
| `SignalRanked { signal, window }` | Pre-orders candidates by a signal's windowed aggregate | custom | | `SignalRanked { signal, window }` | Pre-orders candidates by a signal's windowed aggregate | custom |
| `Hybrid` | Fuses lexical + vector retrieval | the `search` query path (RRF fusion in the search executor) | | `Hybrid` | Fuses lexical + vector retrieval | the `search` query path (RRF fusion in the search executor) |
| `CohortTrending` | Cohort-scoped signal aggregation | reached via `.cohort("name")` with `cohort_trending` | | `CohortTrending` | Cohort-scoped signal aggregation | reached via `.cohort("name")` with `cohort_trending` |

View File

@ -9,7 +9,7 @@ ARCHITECTURE, API, QUICKSTART, CODING_GUIDELINES, thoughts) live at the
## Component specs — `specs/` ## Component specs — `specs/`
The authoritative component specifications (status: Implemented, M0M8). The authoritative component specifications (status: Implemented, M0M12).
| # | Spec | # | Spec | | # | Spec | # | Spec |
|---|------|---|------| |---|------|---|------|
@ -24,8 +24,8 @@ The authoritative component specifications (status: Implemented, M0M8).
## Planning — `planning/` ## Planning — `planning/`
- [**ROADMAP.md**](planning/ROADMAP.md) — milestones M0M11, phase status, known gaps - [**ROADMAP.md**](planning/ROADMAP.md) — milestones M0M12, phase status, known gaps
- [**roadmap-to-cluster.md**](roadmap-to-cluster.md) — adopted M11 plan: gap analysis + phase specs taking the multi-process cluster from experimental to enterprise-grade (m11p1p5 ✅; p6p9 planned), grounded in the 2026-06-10 live stress-test baselines - [**roadmap-to-cluster.md**](roadmap-to-cluster.md) — adopted M11 plan: gap analysis + phase specs taking the multi-process cluster from experimental to enterprise-grade (all nine phases m11p1p9 ✅, 2026-06-13), grounded in the 2026-06-10 live stress-test baselines
- [PRODUCT_ROADMAP.md](planning/PRODUCT_ROADMAP.md) · [architecture-review.md](planning/architecture-review.md) · [roadmap-cohort-analysis.md](planning/roadmap-cohort-analysis.md) · [site-cohort-analysis.md](planning/site-cohort-analysis.md) - [PRODUCT_ROADMAP.md](planning/PRODUCT_ROADMAP.md) · [architecture-review.md](planning/architecture-review.md) · [roadmap-cohort-analysis.md](planning/roadmap-cohort-analysis.md) · [site-cohort-analysis.md](planning/site-cohort-analysis.md)
- Per-milestone phase/task archive: `planning/milestone-0,1,2,3,5,7,8,9,10,11,p/` - Per-milestone phase/task archive: `planning/milestone-0,1,2,3,5,7,8,9,10,11,p/`

View File

@ -602,9 +602,11 @@ always-current HTTP reference is the served spec at `GET /openapi.json` (unauthe
> **On cluster mode — be honest with yourself.** tidalDB's cluster mode is > **On cluster mode — be honest with yourself.** tidalDB's cluster mode is
> **experimental**. It now has a **multi-process** mode (`--region`, one process > **experimental**. It now has a **multi-process** mode (`--region`, one process
> per region, real gRPC peering, real process isolation), plus the single-process > per region, real gRPC peering, real process isolation), plus the single-process
> dev/demo default. Even multi-process mode is *not* quorum-acked HA: writes are > dev/demo default. Multi-process mode is quorum-acked HA: writes are
> leader-durable only and failover is operator-driven (`/cluster/promote`), not > **quorum-acked durable by default** (majority-committed before ack) and leadership
> automatic. It also replicates **global signals only**`user_id` / `creator_id` > failover is **automatic** (Raft-style election — no operator intervention), with
> **elastic seed-join** membership for adding nodes without editing the topology. It
> also replicates **global signals only**`user_id` / `creator_id`
> contexts are rejected on the cluster `/signals` path, so the personalized swipe > contexts are rejected on the cluster `/signals` path, so the personalized swipe
> loop (section 4) is a **single-node** feature today (the cross-region escape > loop (section 4) is a **single-node** feature today (the cross-region escape
> hatch for user-scoped hides is `/hardnegs` + `/cluster/reconcile`). Ship the For > hatch for user-scoped hides is `/hardnegs` + `/cluster/reconcile`). Ship the For

View File

@ -287,6 +287,7 @@ You can load `/openapi.json` into any OpenAPI viewer (Swagger UI, Redoc, Stoplig
| `POST /signals` | yes | `204` | Body: `{ "entity_id", "signal", "weight", "user_id"?, "creator_id"? }`. | | `POST /signals` | yes | `204` | Body: `{ "entity_id", "signal", "weight", "user_id"?, "creator_id"? }`. |
| `GET /feed` | yes | `200` | `?user_id=&profile=for_you&limit=20`. | | `GET /feed` | yes | `200` | `?user_id=&profile=for_you&limit=20`. |
| `GET /search` | yes | `200` | `?query=&user_id=&limit=20`. | | `GET /search` | yes | `200` | `?query=&user_id=&limit=20`. |
| `POST /vector_search` | yes | `200` | Pure k-NN. Body: `{ "vector": [<f32>, ...], "k"?, "ef_search"? }`. |
`limit` is clamped to `1000` at the trust boundary. Request middleware: 30 s timeout (`408`), 100 max in-flight (`429`), 2 MB body cap (`413`), and an `x-request-id` echoed on every response. `limit` is clamped to `1000` at the trust boundary. Request middleware: 30 s timeout (`408`), 100 max in-flight (`429`), 2 MB body cap (`413`), and an `x-request-id` echoed on every response.

View File

@ -210,3 +210,54 @@ groups:
annotations: annotations:
summary: "Self-driving heal not converging" summary: "Self-driving heal not converging"
description: "{{ $value }} peer(s) have been mid-heal for 10m. The heal loop retries through breaker resets automatically; a peer stuck this long is a real partition or a dead node — investigate the link, do NOT re-issue heal by hand." description: "{{ $value }} peer(s) have been mid-heal for 10m. The heal loop retries through breaker resets automatically; a peer stuck this long is a real partition or a dead node — investigate the link, do NOT re-issue heal by hand."
- alert: TidalDBClusterPeerShipStall
# A peer stops accepting batches (its acked frontier flatlines) while
# flushed events queue behind it: deriv() is the gauge-correct way to
# detect a flat acked seqno (rate()/increase() are invalid on gauges).
expr: deriv(tidaldb_cluster_peer_acked_seqno[2m]) == 0 and tidaldb_cluster_peer_ship_queue_depth > 0
for: 2m
labels: { severity: critical }
annotations:
summary: "Replication to a peer has stalled with events queued"
description: "Peer {{ $labels.peer_shard }} stopped accepting batches while events queue behind it (partition, dead peer, or paused sender). Check /cluster/status and the self-heal loop."
- alert: TidalDBClusterQuorumLag
expr: (tidaldb_cluster_relay_last_seq - tidaldb_cluster_relay_durable_seq) > 10000
for: 2m
labels: { severity: critical }
annotations:
summary: "Quorum commit index is far behind the leader frontier"
description: "{{ $value }} events between the leader's flushed frontier and the quorum commit index (threshold 10000). A majority of the replica set is not confirming durability (down/partitioned followers, or follower apply throughput exhausted). ack=quorum writes will 503; the bodies name the laggards."
- alert: TidalDBClusterDivergenceQuarantine
# 1 = the node fenced itself from the data plane after detecting a
# divergent suffix. Since m11p5 it auto-reseeds on next boot; a persistent
# latch means the reseed is not completing.
expr: tidaldb_cluster_divergence_quarantined == 1
for: 2m
labels: { severity: critical }
annotations:
summary: "A node has quarantined itself for a divergent suffix"
description: "A node fenced itself from the data plane after detecting a divergent suffix. Since m11p5 it auto-reseeds on its next boot; if the latch persists, the reseed is not completing — check tidaldb_cluster_reseed_required and the snapshot path."
- alert: TidalDBClusterReseedPending
# Durable reseed-marker latch. Expected briefly after a quarantine or a
# behind-a-compacted-leader restart; a persistent latch means the snapshot
# fetch is failing.
expr: tidaldb_cluster_reseed_required == 1
for: 10m
labels: { severity: warning }
annotations:
summary: "A node has a reseed pending but has not completed it"
description: "A node has latched the reseed marker but has not completed a snapshot reseed in 10m. Expected briefly after a quarantine or a behind-a-compacted-leader restart; a persistent latch means the snapshot fetch is failing (no reachable leader, capability gate, or staging fault)."
- alert: TidalDBClusterSnapshotPinForceDrop
# A staged-snapshot retention pin was force-dropped past the hard cap — a
# joiner started a reseed and never released (died mid-fetch). The dropped
# pin protects compaction.
expr: increase(tidaldb_cluster_snapshot_pin_force_drops_total[1h]) > 0
labels: { severity: warning }
annotations:
summary: "A staged-snapshot retention pin was force-dropped"
description: "A staged-snapshot retention pin was force-dropped past the hard cap in the last hour — a joiner started a reseed and never released (died mid-fetch). The dropped pin protects compaction; the stranded joiner must be re-driven or removed."

View File

@ -20,7 +20,7 @@ This is the routing table `kubernetes.md` points operators to for **cluster** in
| **Read-SLA collapse** | p99 read latency blows past 10 ms; reads hang. | Was CPU oversubscription, fixed in **rc12** (SEARCH_GATE + parallel scatter + cpu limit 2→3). Confirm running image is rc12+ and that load is spread across **all 3** pods (region-pinned reads, not pinned to one). See [cluster.md](../runbooks/cluster.md). | | **Read-SLA collapse** | p99 read latency blows past 10 ms; reads hang. | Was CPU oversubscription, fixed in **rc12** (SEARCH_GATE + parallel scatter + cpu limit 2→3). Confirm running image is rc12+ and that load is spread across **all 3** pods (region-pinned reads, not pinned to one). See [cluster.md](../runbooks/cluster.md). |
| **PVC loss / corruption on one pod** | One pod's data dir is corrupt or its PVC is gone. | Delete that pod's **PVC and pod**; it reseeds fresh from the quorum (snapshot install) and converges to lag=0. On the live image the corrupt-PVC case is recoverable this way — no full-cluster action needed. See [cluster.md — reseed](../runbooks/cluster.md#membership-verbs-m11p5--online-add--remove--inspect--reseed). | | **PVC loss / corruption on one pod** | One pod's data dir is corrupt or its PVC is gone. | Delete that pod's **PVC and pod**; it reseeds fresh from the quorum (snapshot install) and converges to lag=0. On the live image the corrupt-PVC case is recoverable this way — no full-cluster action needed. See [cluster.md — reseed](../runbooks/cluster.md#membership-verbs-m11p5--online-add--remove--inspect--reseed). |
| **Full-cluster loss / rebuild from backup** | Quorum cannot be restored from surviving pods (multi-node data loss). | Rebuild from object store with `tidalctl restore` (BLAKE3-manifested, per-shard `checkpoint_seq`, proven against real S3). See [disaster-recovery.md](../runbooks/disaster-recovery.md). | | **Full-cluster loss / rebuild from backup** | Quorum cannot be restored from surviving pods (multi-node data loss). | Rebuild from object store with `tidalctl restore` (BLAKE3-manifested, per-shard `checkpoint_seq`, proven against real S3). See [disaster-recovery.md](../runbooks/disaster-recovery.md). |
| **Point-in-time recovery (PITR)** | Need to restore the corpus to a specific point in time. | Use the `tidalctl` + DR runbook PITR procedure. See [disaster-recovery.md](../runbooks/disaster-recovery.md). | | **Restore to a backup's checkpoint** | Need to roll the corpus back to a captured backup. | Arbitrary point-in-time recovery (`tidalctl replay --until <seq>`) is **not shipped**. Restore lands you at the backup's `checkpoint_seq` + whatever WAL tail was captured — not an arbitrary instant. Use `tidalctl backup` / `tidalctl restore`. See [disaster-recovery.md — PITR posture](../runbooks/disaster-recovery.md#pitr-posture). |
--- ---

View File

@ -37,7 +37,8 @@ A single embeddable database can replace the 6-system content ranking stack by t
| M8 | Distributed Fabric | Multi-region, multi-tenant replication keeps agent-memory semantics intact | Hosted tidalDB, cloud/edge deployments, shared agent substrate — **✅ COMPLETE**: in-process primitives + multi-node replication over real gRPC + true multi-process cluster mode (one process per region, real process isolation) with full tier-3 UAT (partition injection via TCP-proxy, clock-skew, rolling-upgrade, runbook verification); G1 + G2 resolved. Post-M8 follow-ups: quorum-ack writes, automatic failure detection / leader election | | M8 | Distributed Fabric | Multi-region, multi-tenant replication keeps agent-memory semantics intact | Hosted tidalDB, cloud/edge deployments, shared agent substrate — **✅ COMPLETE**: in-process primitives + multi-node replication over real gRPC + true multi-process cluster mode (one process per region, real process isolation) with full tier-3 UAT (partition injection via TCP-proxy, clock-skew, rolling-upgrade, runbook verification); G1 + G2 resolved. Post-M8 follow-ups: quorum-ack writes, automatic failure detection / leader election |
| M9 | Community Sync & Revocation | Local embeddable profiles can opt into community personalization and safely leave/purge contributions | Community personalization, federated taste graphs, shared feeds — ✅ COMPLETE (2026-06-06) | | M9 | Community Sync & Revocation | Local embeddable profiles can opt into community personalization and safely leave/purge contributions | Community personalization, federated taste graphs, shared feeds — ✅ COMPLETE (2026-06-06) |
| M10 | Governance & Agent Rights | Community rules and agent-scoped permissions control what signals influence ranking | User-owned AI personalization at scale, policy-compliant agents — ✅ COMPLETE (2026-06-06) | | M10 | Governance & Agent Rights | Community rules and agent-scoped permissions control what signals influence ranking | User-owned AI personalization at scale, policy-compliant agents — ✅ COMPLETE (2026-06-06) |
| M11 | Enterprise-Grade Cluster | The multi-process cluster becomes a system of record: fast unified log, quorum-durable, self-healing, horizontally scalable, secured, observable, chaos-tested | Paying-customer cluster deployments — **IN PROGRESS**: m11p1 ✅ + m11p2 ✅ + m11p3 ✅ + m11p4 ✅ (automatic failover: Raft-style election + term fencing + divergence quarantine — closes **G5**; v0.9 "Credible HA" wave complete 2026-06-12) + m11p5 ✅ (membership/discovery/elasticity: DNS peers, snapshot+stream reseed, kind-4 membership records, seed join, `k8s/cluster/` — 2026-06-12) + m11p7 ✅ (security hardening: gRPC mTLS default + zero-drop cert rotation, inter-node HTTP TLS + per-node signed tokens, admin audit log, per-principal rate limit — 2026-06-13) + m11p8 ✅ (observability + operations: completed `tidaldb_cluster_*` set incl. breaker/forwards/self-heal on the per-node `/metrics` listener + Grafana cluster row + alert group, request-id/tracing across hops, truthful status, self-driving heal, WAL PITR archival + `tidalctl` backup/restore, rolling-upgrade version handshake + Woodpecker release gate — closes **G-O** — 2026-06-13) + m11p9 ✅ (continuous correctness: new fault classes (disk-full / slow-fsync / asymmetric partition) as REAL faults behind a production-compiled-out `fault-injection` feature, first-class invariant checkers, `tidal-stress` soak with regression gates + JSON summary, a Woodpecker cron nightly chaos+soak pipeline, and a guarantee→test traceability matrix — closes the **G-C** apparatus; the 30-day-green calendar accrues nightly — 2026-06-13) + m11p6 ✅ (sharding × replication + rebalancing: ONE hash-routed + replicated write surface across S shard groups each at RF with its own elected leader; per-group rebalancing verbs (`/cluster/shards/{id}/transfer` + `/replicas`) and a `?shard=` admin selector; tier-3 3×3 kill-node exit gate — only the dead node's leaderships move, reads never stop, zero acked loss; ≥5,000/s + 2.5× scaling Ref-A-pending — 2026-06-13). **ALL NINE PHASES COMPLETE**; the v1.0 bar now waits only on the 30-day-green nightly calendar (m11p9) and the standing Ref-A/k3s throughput re-runs | | M11 | Enterprise-Grade Cluster | The multi-process cluster becomes a system of record: fast unified log, quorum-durable, self-healing, horizontally scalable, secured, observable, chaos-tested | Paying-customer cluster deployments — **✅ COMPLETE (all nine phases, 2026-06-13)**: m11p1 ✅ + m11p2 ✅ + m11p3 ✅ + m11p4 ✅ (automatic failover: Raft-style election + term fencing + divergence quarantine — closes **G5**; v0.9 "Credible HA" wave complete 2026-06-12) + m11p5 ✅ (membership/discovery/elasticity: DNS peers, snapshot+stream reseed, kind-4 membership records, seed join, `k8s/cluster/` — 2026-06-12) + m11p7 ✅ (security hardening: gRPC mTLS default + zero-drop cert rotation, inter-node HTTP TLS + per-node signed tokens, admin audit log, per-principal rate limit — 2026-06-13) + m11p8 ✅ (observability + operations: completed `tidaldb_cluster_*` set incl. breaker/forwards/self-heal on the per-node `/metrics` listener + Grafana cluster row + alert group, request-id/tracing across hops, truthful status, self-driving heal, WAL PITR archival + `tidalctl` backup/restore, rolling-upgrade version handshake + Woodpecker release gate — closes **G-O** — 2026-06-13) + m11p9 ✅ (continuous correctness: new fault classes (disk-full / slow-fsync / asymmetric partition) as REAL faults behind a production-compiled-out `fault-injection` feature, first-class invariant checkers, `tidal-stress` soak with regression gates + JSON summary, a Woodpecker cron nightly chaos+soak pipeline, and a guarantee→test traceability matrix — closes the **G-C** apparatus; the 30-day-green calendar accrues nightly — 2026-06-13) + m11p6 ✅ (sharding × replication + rebalancing: ONE hash-routed + replicated write surface across S shard groups each at RF with its own elected leader; per-group rebalancing verbs (`/cluster/shards/{id}/transfer` + `/replicas`) and a `?shard=` admin selector; tier-3 3×3 kill-node exit gate — only the dead node's leaderships move, reads never stop, zero acked loss; ≥5,000/s + 2.5× scaling Ref-A-pending — 2026-06-13). **ALL NINE PHASES COMPLETE**; the v1.0 bar now waits only on the 30-day-green nightly calendar (m11p9) and the standing Ref-A/k3s throughput re-runs |
| M12 | Vector Retrieval at Production Shape | Ranking-at-scale steers on measured numbers, not absent ones: ANN candidate generation in RETRIEVE, honored per-query `ef_search`, a recall/latency/memory frontier measured at 1536-D against an exact oracle, sharded ingestion, cluster elasticity, and multi-vector user preference | Relevant, bounded feeds as the corpus grows past the scan cap — **✅ COMPLETE (2026-06-14 / 2026-06-23)**: m12p1 ✅ (read-recall harness: `vector_search_items` k-NN probe + `POST /vector_search` + `tidal-stress --verify-recall` brute-force oracle) + m12p2 ✅ (ANN candidate-gen in RETRIEVE: `for_you` via preference vector, `related` via seed embedding, cached per-signal-type top-K for `trending`; graceful scan-fallback) + m12p3 ✅ (index tuning: per-query `ef_search` honored, dimension-aware brute→HNSW crossover, F16 validated <1%, Int8 rejected) + m12p4 (sharded ingestion: scatter-gather pool + cross-shard unified reads, 3-group `shards:`) + m12p5 (idle-readiness: leader heartbeat carries live frontier so followers converge readiness on idle) + m12p6 (TLS scale-up: real mTLS `kubectl scale 3→5` seed-join over k8s, two-tier cert-manager PKI) + multi-vector user preference (online k-means clusters with DP-means split, per-cluster decayed importance, top-M ANN fan-out; cold-start single-vector fallback). v1.0 bar shared with M11: 30-day-green nightly calendar + standing Ref-A/k3s throughput re-runs |
### Embeddable → Distributed Path ### Embeddable → Distributed Path
@ -153,9 +154,9 @@ The roadmap now has two tracks:
**iknowyou / Aeries: IN PROGRESS (as of 2026-02-24)** — M1M4 complete. M5 (Communication Brief) is in progress with core implementation live; acceptance validation pending. **iknowyou / Aeries: IN PROGRESS (as of 2026-02-24)** — M1M4 complete. M5 (Communication Brief) is in progress with core implementation live; acceptance validation pending.
**Engine status:** M0M10 **COMPLETE**. M9 (Community Sync & Revocation) and M10 (Governance & Agent Rights) shipped 2026-06-06 — see *Implementation Status (M9M10 as-built)* below. **Engine status:** M0M12 **COMPLETE**. M9/M10 shipped 2026-06-06; M11 (Enterprise-Grade Cluster, all nine phases) closed 2026-06-13; M12 (Vector Retrieval at production shape) closed 2026-06-14 / 2026-06-23 — see the M11/M12 milestone rows above and *Implementation Status* below.
**Next (engine):** the cross-cutting community-overlay UAT surface — a single RETRIEVE that blends local + community layers and folds membership-epoch / policy-version / purge-watermark inline into `Results.policy_metadata` (the per-phase mechanisms and direct read APIs all exist; this is the unifying query surface). Then a multi-node M9/M10 UAT harness, and the deferred `writer_agent` u16 interning on the WAL v3 envelope. **Next (engine):** close the v1.0 bar — the 30-day-green nightly chaos+soak calendar (m11p9) and the standing Ref-A/k3s throughput re-runs (≥5,000/s + ≥2.5× single-shard scaling). Deferred follow-ups: the `writer_agent` u16 interning on the WAL v3 envelope and the offline medoid-recluster tier for multi-vector preference.
**Next (product):** iknowyou M5 acceptance pass, then M6 Closed Loop (session lifecycle + preference drift validation). **Next (product):** iknowyou M5 acceptance pass, then M6 Closed Loop (session lifecycle + preference drift validation).
--- ---

View File

@ -70,7 +70,8 @@ are **accurate but DESIGN-REFERENCE — they are not yet loaded by the live
Prometheus.** The rules already cover the must-watch signals Prometheus.** The rules already cover the must-watch signals
(`TidalDBClusterBreakerOpen` on `peer_breaker_state == 1`, (`TidalDBClusterBreakerOpen` on `peer_breaker_state == 1`,
`TidalDBClusterCommitIndexStall`, `TidalDBClusterElectionChurn`, `TidalDBClusterCommitIndexStall`, `TidalDBClusterElectionChurn`,
`TidalDBClusterQuorumTimeouts`, `DivergenceQuarantine`, `ReseedPending`). `TidalDBClusterQuorumTimeouts`, `TidalDBClusterDivergenceQuarantine`,
`TidalDBClusterReseedPending`).
**Open infra step:** promote them into the observability stack as a `PrometheusRule` **Open infra step:** promote them into the observability stack as a `PrometheusRule`
(or vmalert rule file) so they actually page. Until then, on-call watches the (or vmalert rule file) so they actually page. Until then, on-call watches the

View File

@ -1,6 +1,6 @@
# 00 -- Architecture Overview # 00 -- Architecture Overview
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Author:** tidalDB Engineering **Author:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Purpose:** Show how the 14 specs connect. The forest before the trees. **Purpose:** Show how the 14 specs connect. The forest before the trees.

View File

@ -1,6 +1,6 @@
# Storage Engine Specification # Storage Engine Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Author:** tidalDB Engineering **Author:** tidalDB Engineering
**Last Updated:** 2026-05-28 **Last Updated:** 2026-05-28
**Prerequisites:** [VISION.md](../../VISION.md), [thoughts.md](../../thoughts.md), [Signal Ledger Research](../research/tidaldb_signal_ledger.md) **Prerequisites:** [VISION.md](../../VISION.md), [thoughts.md](../../thoughts.md), [Signal Ledger Research](../research/tidaldb_signal_ledger.md)

View File

@ -1,6 +1,6 @@
# Signal System Specification # Signal System Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** WAL subsystem, Entity Store, Schema Engine **Depends on:** WAL subsystem, Entity Store, Schema Engine

View File

@ -1,6 +1,6 @@
# 05 -- Cohort Specification # 05 -- Cohort Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** Entity Model (02), Signal System (03), Query Engine **Depends on:** Entity Model (02), Signal System (03), Query Engine

View File

@ -1,6 +1,6 @@
# Text Retrieval Specification # Text Retrieval Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** Storage Engine (01), Entity Model (02), Signal System (03) **Depends on:** Storage Engine (01), Entity Model (02), Signal System (03)

View File

@ -1,6 +1,6 @@
# Vector Retrieval Specification # Vector Retrieval Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Author:** tidalDB Engineering **Author:** tidalDB Engineering
**Last Updated:** 2026-05-28 **Last Updated:** 2026-05-28
**Depends on:** Storage Engine (01), Entity Model (02), Signal System (03) **Depends on:** Storage Engine (01), Entity Model (02), Signal System (03)

View File

@ -1,6 +1,6 @@
# 08 -- Query Engine Specification # 08 -- Query Engine Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** Storage Engine (01), Entity Model (02), Signal System (03), Relationships (04), Cohorts (05), Text Retrieval (06), Vector Retrieval (07) **Depends on:** Storage Engine (01), Entity Model (02), Signal System (03), Relationships (04), Cohorts (05), Text Retrieval (06), Vector Retrieval (07)

View File

@ -1,6 +1,6 @@
# Ranking and Scoring Specification # Ranking and Scoring Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** Signal System (03), Relationships (04), Cohorts (05), Text Retrieval (06), Vector Retrieval (07) **Depends on:** Signal System (03), Relationships (04), Cohorts (05), Text Retrieval (06), Vector Retrieval (07)

View File

@ -1,6 +1,6 @@
# Feedback Loop Specification # Feedback Loop Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** [Signal System](03-signal-system.md), [Entity Model](02-entity-model.md), [Relationships](04-relationships.md), [Storage Engine](01-storage-engine.md) **Depends on:** [Signal System](03-signal-system.md), [Entity Model](02-entity-model.md), [Relationships](04-relationships.md), [Storage Engine](01-storage-engine.md)

View File

@ -1,6 +1,6 @@
# Schema Specification # Schema Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Author:** tidalDB Engineering **Author:** tidalDB Engineering
**Last Updated:** 2026-05-28 **Last Updated:** 2026-05-28
**Prerequisites:** [02-entity-model.md](02-entity-model.md), [03-signal-system.md](03-signal-system.md), [04-relationships.md](04-relationships.md), [API.md](../../API.md) **Prerequisites:** [02-entity-model.md](02-entity-model.md), [03-signal-system.md](03-signal-system.md), [04-relationships.md](04-relationships.md), [API.md](../../API.md)

View File

@ -1,6 +1,6 @@
# 12 -- Cold Start Specification # 12 -- Cold Start Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** [Entity Model](02-entity-model.md), [Signal System](03-signal-system.md), [Relationships](04-relationships.md), [Cohorts](05-cohorts.md), [Feedback Loop](10-feedback-loop.md), [Schema](11-schema.md) **Depends on:** [Entity Model](02-entity-model.md), [Signal System](03-signal-system.md), [Relationships](04-relationships.md), [Cohorts](05-cohorts.md), [Feedback Loop](10-feedback-loop.md), [Schema](11-schema.md)

View File

@ -1,6 +1,6 @@
# 13 -- Concurrency Specification # 13 -- Concurrency Specification
**Status:** Implemented (M0M8) **Status:** Implemented (M0M12)
**Authors:** tidalDB Engineering **Authors:** tidalDB Engineering
**Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28 **Date:** 2026-02-20 (spec) · Implemented as of 2026-05-28
**Depends on:** [Storage Engine](01-storage-engine.md), [Signal System](03-signal-system.md), [Feedback Loop](10-feedback-loop.md) **Depends on:** [Storage Engine](01-storage-engine.md), [Signal System](03-signal-system.md), [Feedback Loop](10-feedback-loop.md)

View File

@ -1,6 +1,6 @@
# Scale Architecture Specification # Scale Architecture Specification
**Status:** Implemented (M0M8); multi-node cluster mode shipped through M11 — quorum-durable writes (m11p3), automatic election/fencing (m11p4), dynamic membership (m11p5), and **sharding × replication (m11p6): S shard groups, each a replication group at RF with its own elected leader, any gateway hash-routing writes (the §4 Option-C shape), per-group rebalancing verbs.** The cluster `ShardReplica` (`tidal-server/src/cluster/node.rs`) is one group's machinery; a `ClusterNode` hosts a `BTreeMap<ShardId, Arc<ShardReplica>>`. See [docs/roadmap-to-cluster.md](../roadmap-to-cluster.md) and [docs/planning/milestone-11/phase-6.md](../planning/milestone-11/phase-6.md). Original M8 known gaps G3 (shard-routing hash unification) and G6 (embedding 500→400) remain open. **Status:** Implemented (M0M12); multi-node cluster mode shipped through M11 — quorum-durable writes (m11p3), automatic election/fencing (m11p4), dynamic membership (m11p5), and **sharding × replication (m11p6): S shard groups, each a replication group at RF with its own elected leader, any gateway hash-routing writes (the §4 Option-C shape), per-group rebalancing verbs.** The cluster `ShardReplica` (`tidal-server/src/cluster/node.rs`) is one group's machinery; a `ClusterNode` hosts a `BTreeMap<ShardId, Arc<ShardReplica>>`. See [docs/roadmap-to-cluster.md](../roadmap-to-cluster.md) and [docs/planning/milestone-11/phase-6.md](../planning/milestone-11/phase-6.md). Original M8 known gaps G3 (shard-routing hash unification) and G6 (embedding 500→400) remain open.
**Author:** tidalDB Engineering **Author:** tidalDB Engineering
**Last Updated:** 2026-05-28 **Last Updated:** 2026-05-28
**Depends on:** Storage Engine (01), Entity Model (02), Signal System (03), Cohorts (05), Vector Retrieval (07) **Depends on:** Storage Engine (01), Entity Model (02), Signal System (03), Cohorts (05), Vector Retrieval (07)

View File

@ -103,6 +103,38 @@ if [ -f docs/planning/ROADMAP.md ]; then
done done
fi fi
# ---------------------------------------------------------------------------
# 5. Milestone-status freshness (hard). A core doc must not advertise a status
# bound behind ROADMAP's latest COMPLETE milestone (catches the "M0M10
# shipped" / "Implemented, M0M8" drift). Only status-context lines are
# checked, so historical path lists ("M0M2 Embed & prove primitives")
# never trip it. Self-updating: the bound is derived from ROADMAP, not pinned.
# ---------------------------------------------------------------------------
stale_ms=0
latest=0
if [ -f docs/planning/ROADMAP.md ]; then
for n in $(grep -oE 'M[0-9]+' docs/planning/ROADMAP.md | sed 's/M//' | sort -un); do
if grep -qiE "\| *\*?\*?M$n\b.*(COMPLETE|✅)" docs/planning/ROADMAP.md 2>/dev/null; then
[ "$n" -gt "$latest" ] && latest=$n
fi
done
if [ "$latest" -gt 0 ]; then
for f in CLAUDE.md README.md docs/README.md; do
[ -f "$f" ] || continue
while IFS=: read -r ln text; do
echo "$text" | grep -qiE 'status|shipped|implemented|milestones?' || continue
k=$(echo "$text" | grep -oE 'M0(|-)M[0-9]+' | grep -oE '[0-9]+$' | head -1)
[ -z "$k" ] && continue
if [ "$k" -lt "$latest" ]; then
err "$f:$ln advertises status bound M0M$k but ROADMAP's latest COMPLETE milestone is M$latest — update the status line"
stale_ms=1
fi
done < <(grep -nE 'M0(|-)M[0-9]+' "$f")
done
fi
fi
[ "$stale_ms" -eq 0 ] && ok "core-doc milestone-status lines current (latest COMPLETE = M$latest)"
if [ "$fail" -ne 0 ]; then if [ "$fail" -ne 0 ]; then
echo "doc-guard: FAILED — fix the ✗ items above." >&2 echo "doc-guard: FAILED — fix the ✗ items above." >&2
exit 1 exit 1