The suite had not been run since 2026-08-23 and deps were not installed. Running it
against the freshly rolled m12-vsc-20260830 found four failures. Every one was the
harness doing its job; three were stale pins it explicitly told me to invert.
REAL FINDING, caught by the suite and nothing else: tidaldb-2 was NotReady mid-run.
It had exited(0) with {"reason":"reseed_self_restart","shard":1}, reinstalled a
snapshot and converged. Designed behavior - but the suite sampled readiness ONCE and
reported a self-healing cluster as broken. Readiness is now polled via
waitForPodsReady with a bounded budget and the whole timeline attached as evidence.
Deliberately not Playwright retries: retries:0 is correct here, because a live check
that only passes on attempt two has told you something true.
STALE PINS INVERTED (each verified live first, not taken on the message's word):
- 06-logs: ANSI escapes are gone (0 in a 5-line sample), BUG-006 resolved on this
image. Now pinned so a regression to coloured output fails.
- 09-operator-authority + CAP-015 capture: tidaldb_http_* exists (185 series
against a 552 baseline). Runbook 9.1 moved from inert to LIVE. CAP-015 keeps its
purpose - state the gaps - and now names the one that is still real: no JSON_LOGS.
- The transient /search 500 and public 502 were tidaldb-2's restart window, not
defects; both surfaces returned 200 on eight retries afterwards.
THRESHOLD CALIBRATED AGAINST A MEASUREMENT, TWICE. My first fix capped
consecutive ship failures at 500, guessing a restart burst was ~100. Measurement
killed it: a reseed restart is a ~2 minute absence, which at the shipper's 100ms
cadence is ~1200-2000 failures - observed exactly 1950, then "peer recovered", with
peer_acked_seqno back at the frontier. A COUNT cannot separate "a peer restarted"
from "shipping is stuck"; it only encodes how long the peer was away. The test now
compares the newest distress line against the newest recovery line and fails only
when distress is newer. Same correction applied to the alert in k3s-fleet.
STALE EVIDENCE WAS THE WORST GAP. demo/public/captures and capture-manifest.json
still described m12-admin-gate-20260823 - two image rolls stale - while
demo:preflight reported "audited perfect" about week-old frames, and the rendered
title card read "image m12-admin-gate-20260823 - 32 checks green". The capture suite
writes to test-results/demo-captures/ and the copy-and-merge step into the published
set simply did not exist; it was done by hand once. Added demo/promote.ts: copies
frames, verifies each PNG against its fragment hash, and stamps buildRevision and
verifiedImage from the live StatefulSet. Verdicts land `pending`, so preflight fails
until the frames are audited - that failure is the gate. scenes.ts now derives the
image tag and check count from the manifest, and preflight fails if a literal is
pasted back in (proven by pasting one back in).
All 10 captures were opened individually at full resolution; the audit note is stored
in the manifest beside each verdict rather than only in prose.
Green: 34 e2e + 5 hermetic semantics + 10 captures + preflight + 2107 lib.
Video: demo/out/deploy-verification.mp4, 90.05s 1920x1080 h264, title card now
reading "image m12-vsc-20260830 - 34 checks green".
CLAUDE.md gains a Deploy Verification section and AGENTS.md a short mandatory
pointer: every deploy is verified through this harness, and maintaining it is part
of the change, not follow-up. The suite pins current reality including defects, so a
correct improvement WILL turn it red - and that is the harness working.
114 lines
4.4 KiB
TypeScript
114 lines
4.4 KiB
TypeScript
/**
|
|
* Smoke: the shortest path that proves the harness can reach every plane it
|
|
* needs, and that the deployment is fundamentally alive.
|
|
*
|
|
* If this fails, nothing else in the suite is worth reading. It covers:
|
|
* - the cluster plane (kubectl can list the StatefulSet pods)
|
|
* - public DNS + TLS (the hostname resolves and the ingress serves health)
|
|
* - the credential path (the sourced bearer is accepted)
|
|
* - the write path (a quorum-acked write really commits)
|
|
*/
|
|
|
|
import { resolve4 } from 'node:dns/promises';
|
|
import { expect, test } from '@playwright/test';
|
|
import { kubectl, waitForPodsReady } from './support/cluster';
|
|
import { observed, recordJson } from './support/evidence';
|
|
import {
|
|
EXPECTED_NODE_IPS,
|
|
NAMESPACE,
|
|
POD_NAMES,
|
|
PUBLIC_BASE_URL,
|
|
PUBLIC_HOST,
|
|
apiKey,
|
|
} from './support/env';
|
|
|
|
test.describe('smoke', () => {
|
|
test('all three cluster pods are Ready', async ({}, testInfo) => {
|
|
// Polled, not sampled once: `reseed_self_restart: true` means a node that
|
|
// latches a reseed marker legitimately exits(0) and comes back. Observed
|
|
// 2026-08-30 — tidaldb-2 restarted mid-suite with
|
|
// {"reason":"reseed_self_restart","shard":1} and was Ready ~20s later. A
|
|
// single sample turns that self-healing behavior into a red suite.
|
|
const outcome = await waitForPodsReady(NAMESPACE, POD_NAMES);
|
|
|
|
await recordJson(testInfo, 'pod-readiness', {
|
|
converged: outcome.converged,
|
|
final: outcome.final,
|
|
restarts: outcome.restarts,
|
|
samples: outcome.samples,
|
|
note:
|
|
'restartCount is evidence, not a failure: a bounded reseed_self_restart is ' +
|
|
'designed behavior. A CLIMBING count across runs is the livelock signature ' +
|
|
'(the 2026-08-20 incident reached 196).',
|
|
});
|
|
|
|
expect(
|
|
outcome.converged,
|
|
`pods did not all reach Ready within the budget; final=${JSON.stringify(outcome.final)}`,
|
|
).toBe(true);
|
|
expect(
|
|
Object.keys(outcome.final).sort(),
|
|
'expected exactly the known pod set',
|
|
).toEqual([...POD_NAMES].sort());
|
|
});
|
|
|
|
test('public hostname resolves to every node and serves health over valid TLS', async ({
|
|
playwright,
|
|
}, testInfo) => {
|
|
// Real public resolution is part of the claim, so resolve it for real
|
|
// rather than pinning an IP. A pinned IP would still pass if DNS were
|
|
// broken — which is precisely the failure that retired the old hostname.
|
|
const addresses = await resolve4(PUBLIC_HOST);
|
|
await recordJson(testInfo, 'dns-resolution', { host: PUBLIC_HOST, addresses });
|
|
expect(
|
|
[...addresses].sort(),
|
|
'every node IP should answer for the public hostname',
|
|
).toEqual([...EXPECTED_NODE_IPS].sort());
|
|
|
|
// ignoreHTTPSErrors stays false: an invalid or expired certificate must
|
|
// fail this test, not be tolerated.
|
|
const context = await playwright.request.newContext({ ignoreHTTPSErrors: false });
|
|
try {
|
|
const response = await context.get(`${PUBLIC_BASE_URL}/health`);
|
|
await recordJson(testInfo, 'health-response', {
|
|
status: response.status(),
|
|
body: (await response.text()).slice(0, 500),
|
|
});
|
|
expect(response.status(), 'health must be publicly reachable').toBe(200);
|
|
} finally {
|
|
await context.dispose();
|
|
}
|
|
});
|
|
|
|
test('sourced bearer is accepted and a quorum write commits', async ({
|
|
playwright,
|
|
}, testInfo) => {
|
|
const context = await playwright.request.newContext({
|
|
baseURL: PUBLIC_BASE_URL,
|
|
extraHTTPHeaders: { authorization: `Bearer ${apiKey()}` },
|
|
});
|
|
try {
|
|
const read = await context.get('/search', { params: { query: 'smoke', limit: 1 } });
|
|
expect(read.status(), 'authenticated read must succeed').toBe(200);
|
|
|
|
const write = await context.post('/items', {
|
|
headers: { 'content-type': 'application/json', 'x-tidal-ack': 'quorum' },
|
|
data: {
|
|
entity_id: 999_000_001,
|
|
metadata: { title: 'harness smoke', category: 'verification' },
|
|
},
|
|
});
|
|
await recordJson(testInfo, 'quorum-write', {
|
|
status: write.status(),
|
|
body: (await write.text()).slice(0, 500),
|
|
});
|
|
|
|
// 201 means the write was acknowledged by a quorum, not merely accepted
|
|
// by one node. This is the single strongest signal in the suite.
|
|
expect(write.status(), 'quorum-acked write must commit').toBe(201);
|
|
} finally {
|
|
await context.dispose();
|
|
}
|
|
});
|
|
});
|