tidaldb/demo/src/scenes.ts
jordan 9523f6da43 test(e2e): verify ranking semantics with a content-feed app, and route three product findings
The existing 32 checks prove the deployment answers -- TLS, auth, quorum commit,
convergence, isolation, dashboards, backups. Not one wrote a signal and observed
an order change, so VISION.md:17 "Ranking is not a feature. It is a primitive."
was unverified. This adds a 60-item content-feed app and five assertions that
verify the product's semantics, on a hermetic standalone node.

Added
- tests/e2e/app/: fixture contract (60 items, 4 categories, each owning one
  unoccupied 100-id embedding cluster), a deep-module harness owning the whole
  lifecycle behind startApp(), the product page, and an app:dev entry point.
- tidal-stress/src/bin/feed-fixture.rs: seeds the catalog and emits brute-force
  ground truth, reusing recall::embedding_for rather than adding a third copy of
  the corpus generator (tidal/src/db/items.rs already holds a second).
- GroundTruth::from_ids: the oracle now serves sparse id sets. build() delegates,
  so there is no transient copy even at 1M, and top_k indexes positionally.
- 10-ranking-semantics.spec.ts (5 hermetic checks) and
  11-ranking-integrity.spec.ts (2 cluster tripwires).
- playwright.semantics.config.ts + CAP-016 demo beat (walkthrough 82s -> 90s).

Measured, not merely green
- like: index 59 -> 0, like_boost 2.0, with no sleep between write and read.
- decay: implied half-lives 7.0007 d and 14.0014 d against a schema declaring
  7 d and 14 d, recovered from a 4-second window via H = t*ln2 / -ln(v2/v1) and
  compared against the schema the node actually loaded, not a hardcoded copy.
- ANN: top-10 identical to brute-force cosine on all four probes; self-distance
  0.0148-0.0197 against a 0.05 tolerance.
- rank: dense 1..60 on standalone vs [1,1,1,2,2,3,4,3,4,5,6,5] on the cluster.

Three product findings, pinned and routed to @tidal-engineer
- BUG-018 (High) skip is durably accepted and query-time inert. Penalty is fully
  implemented (ranking/profile.rs:227 -> executor/signal_values.rs:183, labelled
  {signal}_penalty at executor/mod.rs:65) but skeleton() sets penalties: vec![]
  (ranking/builtins.rs:62) and none of the 27 built-ins overrides it. So
  VISION.md:187 "negative signals are equal citizens" holds for no shipped
  profile. Same anti-pattern as the reseed defects and scatter_merge: a guard
  present on one path, absent on its sibling.
- BUG-019 (Medium) three built-ins read signals this schema does not declare --
  trending/share_velocity, hidden_gems/completion, controversial/dislike -- so
  those terms are permanently 0 and trending ranks on view_velocity alone.
- BUG-020 (Low) for_you declares Scan{sort_field:"created_at"} but ignores a
  created_at metadata value; an order matching neither id-asc nor
  created_at-desc came back strictly id-ascending.

Two assertions therefore report a gap rather than a success, written as tripwires
whose failure message says what to do when the gap closes. The rank defect is
localised, not fixed: scatter_merge (cluster/node.rs:7542) returns a merged slice
without re-stamping rank while scores stay correctly ordered, so the fault is the
missing stamp and not the merge's sort.

Notes
- Hermetic by construction: its own config, because FullConfig.projects is not
  filtered by --project and globalSetup publishes credentials into the main
  process that forked workers inherit -- so a setup project cannot replace it,
  and weakening globalSetup would destroy the fail-loud behaviour that is its
  purpose. Verified with KUBECONFIG=/nonexistent and all E2E_* unset.
- Never touches the deployed corpus: skip is permanent: true, so seeding it into
  production would be irreversible.
- The page contains no sort, no hostname and no credential; the harness proxy
  injects auth server-side so no bearer reaches a browser or a capture.
- Schema comes from k8s/cluster/schema-configmap.yaml, asserted at 1536 dims;
  tidal-server/config/default-schema.yaml declares 128 and would 422 every write.

Verification: 5 semantics + 34 regression + 10 demo captures green; tsc clean;
tidal-stress clippy clean under clippy::all=deny with unwrap_used=deny; 2101
tidaldb lib tests; preflight 10/10 perfect; render 90.05s/2700 frames with zero
empty boundary frames; zero orphan processes or temp dirs after teardown.
2026-08-23 22:42:02 -06:00

196 lines
6.0 KiB
TypeScript

/**
* Scene metadata — the single source of truth for order, duration, and which
* promoted capture each beat presents. Mirrors demo/storyboard.md; a beat here
* with no storyboard row, or a capture not marked `perfect` in the manifest, is
* a defect the preflight rejects.
*/
export const FPS = 30;
export const WIDTH = 1920;
export const HEIGHT = 1080;
export type AudienceRung = 'need' | 'want' | 'dream';
export type Scene =
| {
kind: 'title';
id: string;
seconds: number;
rung: AudienceRung;
heading: string;
lines: string[];
footer?: string;
}
| {
kind: 'proof';
id: string;
seconds: number;
rung: AudienceRung;
capabilityId: string;
captureId: string;
heading: string;
caption: string;
}
| {
kind: 'recap';
id: string;
seconds: number;
rung: AudienceRung;
heading: string;
lines: string[];
footer?: string;
};
export const scenes: Scene[] = [
{
kind: 'title',
id: 'B1-opening',
seconds: 9,
rung: 'need',
heading: 'tidalDB deploy verification',
lines: [
'Three voters on orchard9-k3sf. One public endpoint. A runbook an operator can walk.',
'Every number that follows came from a command that ran against the live cluster.',
],
footer: 'namespace tidaldb-cluster · image m12-admin-gate-20260823 · 32 checks green',
},
{
kind: 'proof',
id: 'B2-convergence',
seconds: 7,
rung: 'need',
capabilityId: 'CAP-002',
captureId: 'CAP-002-convergence',
heading: 'Every node agrees, and none is behind',
caption:
'Each node asked for its own view. A pod can be Ready while its replication is stalled — that is what the reseed livelock exploited.',
},
{
kind: 'proof',
id: 'B3-boundary-write',
seconds: 8,
rung: 'need',
capabilityId: 'CAP-006',
captureId: 'CAP-006-quorum-write',
heading: 'The boundary holds, and the write commits',
caption:
'Same path, three credentials. Then a write a majority of nodes acknowledged — the one claim a health endpoint cannot fake.',
},
{
kind: 'proof',
id: 'B3a-feed-reorder',
seconds: 8,
rung: 'need',
capabilityId: 'CAP-016',
captureId: 'CAP-016-feed-reorder',
heading: 'And the product it exists to be',
caption:
'Everything before this proves the deployment answers. This is what it is for: one like, the same query again, and the item is first. No ETL between the write and the read — a local node on fixture data, so the claim is the mechanism, not the corpus.',
},
{
kind: 'proof',
id: 'B4-isolation',
seconds: 7,
rung: 'need',
capabilityId: 'CAP-008',
captureId: 'CAP-008-network-isolation',
heading: 'The refusal is the point',
caption:
'A NetworkPolicy that blocks everything is an observability outage. One that blocks nothing is theatre. Both directions proven.',
},
{
kind: 'proof',
id: 'B5-authority',
seconds: 6,
rung: 'want',
capabilityId: 'CAP-014',
captureId: 'CAP-014-authority',
heading: 'An application key cannot remove a member',
caption:
'403, not 401 — the credential is valid, it simply has no operator authority.',
},
{
kind: 'proof',
id: 'B6-dashboard',
seconds: 8,
rung: 'want',
capabilityId: 'CAP-010',
captureId: 'CAP-010-dashboard',
heading: 'The board an operator opens at 3am',
caption:
'Health, reseed state, corpus size, and per-node latency — scoped to the cluster, not to whatever else shares the metric name.',
},
{
kind: 'proof',
id: 'B7-recovery',
seconds: 6,
rung: 'want',
capabilityId: 'CAP-013',
captureId: 'CAP-013-backup',
heading: 'The recovery story is intact',
caption:
'Selected by the schedule label the freshness alert actually watches — not merely the newest backup object.',
},
{
kind: 'proof',
id: 'B8-blind-spot',
seconds: 7,
rung: 'want',
capabilityId: 'CAP-012',
captureId: 'CAP-012-tidalctl',
heading: 'It refuses to invent a number',
caption:
'Two healthy peers would otherwise read as 13.3 million events behind. NO REPORT is an honest "I do not know".',
},
{
kind: 'proof',
id: 'B9a-inert',
seconds: 7,
rung: 'need',
capabilityId: 'CAP-015',
captureId: 'CAP-015-inert',
heading: 'What this deployment does not yet do',
caption:
'Two committed features are absent from the running image. The suite asserts that absence, so the day it changes it fails and says so.',
},
{
kind: 'proof',
id: 'B9-drift-catch',
seconds: 10,
rung: 'dream',
capabilityId: 'CAP-014',
captureId: 'CAP-014-drift',
heading: 'The harness corrected its own runbook',
caption:
'The runbook said the credential split was "not active yet — requires an image roll". On its first run the harness read the live image and the live secret, and proved the gate was already enforcing. The document was wrong; the harness said so before anyone noticed.',
},
{
kind: 'recap',
id: 'B10-recap',
seconds: 7,
rung: 'need',
heading: 'Verified, including the gaps',
lines: [
'32 checks green against the live deployment.',
'Two committed features are absent from the running image — and the suite asserts that absence deliberately.',
'The day it changes, the suite fails and says so.',
],
footer: 'docs/runbooks/deploy-verification.md · npm run test:e2e',
},
];
export const durationInFrames = scenes.reduce(
(total, scene) => total + Math.round(scene.seconds * FPS),
0,
);
/** Frame offset of each scene, derived once so nothing drifts. */
export const timeline = scenes.reduce<{ scene: Scene; from: number; frames: number }[]>(
(acc, scene) => {
const from = acc.length === 0 ? 0 : acc[acc.length - 1].from + acc[acc.length - 1].frames;
acc.push({ scene, from, frames: Math.round(scene.seconds * FPS) });
return acc;
},
[],
);