Testing
MCS needs more than controller unit tests. Its correctness depends on several systems agreeing about desired state: the control plane, Kubernetes, the MCS Operator, ClickHouse, Keeper, object storage, and ElasticQuery. The test strategy therefore uses progressively more realistic layers and keeps the expensive failure tests out of the fast pull-request path.
This document separates three states explicitly:
- automated today: checked-in tests that run without a person interpreting each step;
- manual today: required validation that an engineer can perform, but that does not yet have a reliable automated runner;
- automation target: the CI tier where each manual test must eventually run.
A command sequence written in a runbook is still manual testing if a person must provision the environment, choose the next action, or decide whether the result is correct.
Product modules and test ownership
MCS is one product, but it crosses several independently failing modules. A test result is meaningful only when it identifies which module it proves.
| Product module | Product responsibility | Automated today | Still verified manually |
|---|---|---|---|
| Management control plane | Authentication, organizations, users, Keeper and Service requests, policy, and status projection | Service, repository, handler, authorization, and envtest coverage | Browser workflow from administrator login through organization-user provisioning |
| Runtime Operator | Convert desired-state CRs into Kubernetes resources and continuously reconcile them | Controller, resource-generation, release, status, and envtest coverage | Real scheduling, pod startup, rolling updates, retry, and recovery |
| Keeper | Metadata coordination and dynamic membership for ClickHouse services | Desired resources, versions, configuration, and controller behavior | Real 3/5/7-member quorum, leader loss, anti-affinity, and disruption |
| ClickHouse service | Stateful SQL, DDL, inserts, clusters, credentials, and service-local data | Release configuration, topology, resource generation, and validation | Real SQL on every node, ON CLUSTER, continuous inserts, and lifecycle operations |
| Object storage and table disks | Durable service/replica/table isolation and managed inline table disks | Endpoint construction, credentials references, addressing style, and storage helpers | Real CAIOS reads/writes, table identity, drop/recreate, and stale-data isolation |
| ElasticQuery and schema sync | Read-only query compute and synchronized readers for eligible tables | Resource generation, read-only configuration, discovery, and schema-sync script contracts | Real reader creation, aggregate tables, scale-out, UUID collisions, and failure visibility |
| Console SQL and monitoring | Select compute/node/database, execute SQL, and render status and results | Routes, request policy, query helpers, result limits, authorization, and HTML rendering | Browser usability, long results, downloads, charts, dashboards, and OpenTelemetry workflows |
| Packaging and installation | CRDs, Helm charts, release configuration, and from-zero deployment | Generated-code checks, chart lint/render, and release examples | Fresh Kind/Helm installation and local image availability |
The two most important boundaries are:
- management control-plane tests stop after proving the correct CR or directly owned bootstrap resource;
- Runtime Operator tests start from that CR and prove the resulting runtime resources.
Only a live end-to-end test proves the whole path from a user action to a SQL result.
Test principles
- Test policy and business behavior below the HTTP layer.
- Test Kubernetes reconciliation against a real API server when possible.
- Render every generated resource and Helm chart deterministically.
- Use a real Kind cluster for scheduling, pod lifecycle, networking, storage, ClickHouse, Keeper, and ElasticQuery behavior.
- Treat failure recovery as a tested product contract, not an operator assumption.
- Keep test resources isolated by namespace, service ID, replica ID, and object-storage prefix.
- Preserve logs, events, rendered manifests, and SQL evidence whenever an end-to-end run fails.
Test levels and current status
| Level | Scope | Status today | Automation target |
|---|---|---|---|
| L0 | Formatting, lint, generated code, CRDs, and Helm rendering | Automated | Pull request |
| L1 | Go unit, service, model, and helper behavior | Automated | Pull request |
| L2 | Kubernetes API, repository, controller, and Console integration through envtest | Automated | Pull request |
| L3 | Live Kind provisioning, real pods, ClickHouse SQL, object storage, and ElasticQuery | Manual | Kind smoke |
| L4 | Keeper quorum, rolling updates, pod/node faults, and reconciliation recovery | Manual | Nightly resilience |
| L5 | Operator, ClickHouse, and Keeper upgrade/rollback compatibility | Manual | Release qualification |
The main gap is not another list of manual checks. It is a reusable L3-L5 test harness that can provision the topology, execute actions, assert results, collect evidence, and clean up without human decisions.
Coverage ownership
The complete MCS Control Plane contains two execution domains. The management control plane and Runtime Operator currently ship from the same repository and may run in the same process, but they have different correctness boundaries. Tests must follow those boundaries:
- the management control plane proves that an authenticated user action becomes the correct desired-state object;
- the Runtime Operator proves that a desired-state object becomes the correct, healthy Kubernetes and data-plane resources;
- internal contract and end-to-end tests prove that the CR and status exchanged between these two parts of the Control Plane remain compatible.
Avoid proving Operator reconciliation through Console page tests, and avoid retesting form parsing in controller tests.
Management control plane key points
| Area | Required coverage | Primary level |
|---|---|---|
| Authentication | login, logout, session expiry, invalid credentials, bootstrap administrator | L1/L2 |
| Authorization | administrator-only operations, organization membership, cross-organization denial, public Docs access | L1/L2 |
| Input policy | validation, defaults, supported releases, scale ranges, Keeper selection, organization limits | L1 |
| Service layer | create/update/delete semantics, stable IDs, error mapping, idempotent requests | L1 |
| Repository | Kubernetes CRUD, conflict retry, not-found behavior, list/filter by organization, status reads | L2 |
| Desired state | exact MCSOrganization, MCSKeeper, MCSService, and MCSServiceUser specs produced by each operation |
L1/L2 |
| Direct Kubernetes writes | runtime Namespace and credential Secret creation, ownership, update, and cleanup | L2 |
| Console and future API | request decoding, redirects, validation errors, response model, and equivalent service-layer behavior | L1/L2 |
| Status projection | CR status and conditions rendered without inventing readiness or hiding failures | L1/L2 |
| SQL and monitoring proxy | target selection, credentials, read-only boundaries, query limits, result/error handling | L1/L3 |
The management control-plane suite should stop at the desired-state boundary. It asserts the CR and directly owned bootstrap resources, but it does not claim that a ClickHouse StatefulSet successfully started.
Runtime Operator key points
| Area | Required coverage | Primary level |
|---|---|---|
| CR reconciliation | create, update, delete, finalization, owner references, retries, and idempotency | L2/L3 |
| Resource generation | exact Deployments, StatefulSets, Services, ConfigMaps, Secrets references, PDBs, probes, and resources | L1/L2 |
| Status and conditions | observed generation, progress, readiness, degraded state, terminal errors, and recovery | L2/L3 |
| ClickHouse topology | node identity, cluster membership, all_shards, all_replicas, macros, and dynamic membership |
L1/L3 |
| Storage configuration | service/replica prefixes, virtual-hosted CAIOS access, table-disk settings, credential references | L1/L3 |
| ElasticQuery | read-only configuration, node scaling, mounted storage policies, and query readiness | L1/L3 |
| Schema sync | eligible table discovery, attach/recreate, UUID collision handling, drop/recreate cleanup, and failure logging | L1/L3 |
| Keeper | 3/5/7 topology, hard anti-affinity, membership, quorum, rolling updates, and failure recovery | L1/L3/L4 |
| Rollout safety | changes roll only the intended component; Keeper changes must not roll ClickHouse services | L2/L4 |
| Release behavior | version-specific configuration, upgrade ordering, unsupported downgrade rejection, and rollback evidence | L1/L5 |
| Failure recovery | missing Secrets, unavailable APIs, pod/node loss, operator restart, partial reconciliation, and retry | L2/L4 |
The Runtime Operator suite starts from valid and invalid CRs. It must not depend on a human using the Console to create those fixtures.
Internal contracts and end-to-end points
The boundary between the two components needs a smaller set of high-value contract tests:
- every management control-plane create/update action produces a CR accepted by the current CRD and Runtime Operator;
- the Runtime Operator reports status and conditions the management control plane can project without special-case guessing;
- field names, defaults, release identifiers, Secret references, and ownership rules remain compatible;
- deletion requested through the management control plane reaches Runtime Operator finalization and removes the expected owned resources;
- one end-to-end happy path proves user action → CR → reconciliation → Ready → SQL result;
- one end-to-end failure path proves reconciliation error → CR condition → visible Control Plane error.
These tests detect integration drift without making every management control-plane test wait for pods or every Runtime Operator test start an HTTP server.
Automated today: unit and integration coverage
Go unit and service tests
The Go suite is organized around product modules rather than UI pages. It covers:
- API validation and defaulting;
- management control-plane service behavior and Kubernetes repository adapters;
- authentication, organization membership, cross-organization access, and administrator-only operations;
- ClickHouse service, organization, Keeper, and service-user controllers;
- ClickHouse and ElasticQuery resource generation;
- Runtime Operator status, reconciliation, ownership, and idempotency;
- release definitions and generated-resource examples;
- naming, Kubernetes apply helpers, pod helpers, and storage helpers;
- Console routes, authorization behavior, forms, SQL execution, monitoring, documentation, and query helpers.
Run the repository suite with:
make test
make test regenerates CRDs and deepcopy code, runs formatting and go vet,
starts the Kubebuilder test control plane, executes all Go tests, and writes
cover.out.
The package-level ownership is:
| Package area | Automated contract |
|---|---|
api/v1alpha1 |
CRD validation, defaults, supported field combinations, and serialization |
internal/controlplane/service |
Management operations, policy, identity, organization boundaries, and error semantics |
internal/controlplane/repository/kubernetes |
Kubernetes-backed desired-state CRUD, filtering, conflicts, and status reads |
internal/controlplane/api and internal/controlplane/console |
Authentication/authorization wiring, request parsing, routes, forms, projections, SQL/monitoring helpers, and Docs |
internal/organization, internal/service, internal/keeper, internal/serviceuser |
Controller watches, desired resources, lifecycle behavior, and status |
internal/service/clickhouse and internal/service/elasticquery |
ClickHouse and ElasticQuery Deployments/StatefulSets, Services, configuration, storage, topology, and schema-sync contracts |
internal/service/release and internal/keeper/release |
Version-specific configuration and checked-in generated-resource examples |
internal/util |
Naming, Kubernetes apply/pod helpers, storage paths, and shared invariants |
These tests intentionally mock or omit the scheduler, containers, DNS, networking, object storage, and real ClickHouse SQL. Those belong to the post-install flow, not to a unit-test assertion.
Envtest integration tests
Controller and Console packages use Kubebuilder envtest. These tests run
against a real Kubernetes API server and etcd with the generated MCS CRDs.
They are appropriate for:
- API validation and persistence;
- controller watches and reconciliation;
- status updates and ownership;
- repository read/write behavior;
- HTTP handlers that depend on Kubernetes objects.
Envtest does not provide a scheduler, kubelet, CNI, CSI, real pods, Services, or object storage. A passing envtest suite cannot prove workload readiness, anti-affinity, rolling-update safety, or ClickHouse query behavior.
Generated-resource tests
Release example tests compare generated Kubernetes objects with checked-in expected YAML. These tests catch unintended changes to StatefulSets, Deployments, Services, ConfigMaps, Secrets references, probes, resources, storage settings, and topology.
When an intentional resource change is made, review the complete diff before updating the expected output.
Helm validation
Validate both charts with:
make chart
This lints and renders the operator chart and bootstrap chart. CI also regenerates manifests and fails when generated chart artifacts differ from the committed files.
Current CI gates
The pull-request workflow currently runs:
make lint;- manifest, generated-code, and chart verification with a clean Git diff;
- the Go test and coverage workflow;
- release and image/chart publication only after the validation jobs pass.
These are necessary pull-request gates, but they are not a full live-cluster end-to-end test.
What automated tests do not prove
A passing make test does not prove any of the following:
- that a Pod can be scheduled or an image can be pulled;
- that Keeper elects a leader or retains quorum;
- that ClickHouse accepts SQL over a real Service;
- that CAIOS credentials and virtual-hosted addressing work;
- that a managed table disk is readable from ElasticQuery;
- that schema sync converges after a table is dropped and recreated;
- that a rolling update remains available during a fault;
- that the browser workflow is understandable to a user.
Those checks require the post-install acceptance flow below. Until that flow is automated, the engineer running it is responsible for recording the observed results.
Manual today: required live-cluster testing
The canonical local topology is described in Local installation. End-to-end runs must use the same Helm chart and values structure as deployment; ad-hoc manifests are not a substitute.
The scenarios in this section are product requirements, but they are not currently a complete automated CI suite. Until they are automated, an engineer must run them before relevant releases and retain the evidence described below.
Post-install acceptance walkthrough
Use this section after completing Local installation. It is the minimum product acceptance test: it proves identity and provisioning through the Console, real ClickHouse DDL and inserts, object-backed managed table disks, and ElasticQuery schema sync.
1. Confirm the installed runtime
Before opening the Console, verify that there are no pending or crashing workloads:
kubectl get nodes
kubectl get pods --all-namespaces
kubectl get mcskeepers.data.coreweave.com --all-namespaces
kubectl get mcsorganizations.data.coreweave.com --all-namespaces
kubectl get mcsservices.data.coreweave.com --all-namespaces
Pass criteria:
- every Kubernetes node is
Ready; - the Operator is
Running; - existing Keeper and Service CRs either report
Readyor have an understood in-progress condition; - no workload is in
CrashLoopBackOff,ImagePullBackOff, or an unexpectedPendingstate.
2. Prove administrator and organization-user boundaries
Use the Console rather than applying CRs directly:
- sign in as the bootstrap administrator;
- create a Keeper fleet;
- create an organization, assign that Keeper, and create its owner account;
- sign out;
- sign in as the organization owner;
- create a three-node ClickHouse service with at least one ElasticQuery node;
- wait until the service reports both ClickHouse and ElasticQuery ready;
- confirm the organization user cannot access administrator-only organization or Keeper mutations.
This proves the management control plane and the handoff to the Runtime Operator. Record the organization ID, Keeper ID, Service ID, ClickHouse node IDs, and ElasticQuery node IDs.
3. Inspect the live topology
In SQL Console, select ClickHouse and any node, then run:
SELECT
cluster,
shard_num,
replica_num,
host_name,
host_address,
is_local
FROM system.clusters
WHERE cluster IN ('all_shards', 'all_replicas')
ORDER BY cluster, shard_num, replica_num;
Then prove both dynamic clusters can execute remote queries:
SELECT hostName() AS node
FROM cluster('all_shards', system.one)
ORDER BY node;
SELECT hostName() AS node
FROM clusterAllReplicas('all_replicas', system.one)
ORDER BY node;
Pass criteria: the reported membership matches the ClickHouse nodes shown by the Console, and each query returns every expected node exactly once.
4. Create an ElasticQuery-eligible table
Run the DDL once from a ClickHouse node:
CREATE DATABASE IF NOT EXISTS e2e ON CLUSTER all_replicas
ENGINE = Atomic;
CREATE TABLE e2e.events ON CLUSTER all_replicas
(
id UInt64,
event_time DateTime64(3),
source LowCardinality(String),
body String
)
ENGINE = MergeTree
ORDER BY (event_time, id)
SETTINGS table_disk = true;
Do not specify disk = .... In the managed ClickHouse image,
table_disk = true is the user opt-in and MCS injects the inline
object-storage disk.
Verify the resulting definition on every ClickHouse node:
SHOW CREATE TABLE e2e.events;
Pass criteria: the definition retains table_disk = true, contains the
operator-managed inline object-storage disk, and uses a service/replica/table
path rather than a shared unscoped prefix.
5. Insert distinct data through every ClickHouse node
Select each ClickHouse node explicitly in SQL Console. Run the statement below
once per node, changing the numeric base to 1000, 2000, and 3000:
INSERT INTO e2e.events
SELECT
1000 + number + 1,
now64(3),
getMacro('replica'),
concat('event-', toString(number + 1))
FROM numbers(10);
On the same node, verify its local data:
SELECT
hostName() AS node,
count() AS rows,
min(id) AS first_id,
max(id) AS last_id,
groupArrayDistinct(source) AS sources
FROM e2e.events
GROUP BY node;
Pass criteria:
- every ClickHouse node has exactly 10 local rows;
- ID ranges are
1001-1010,2001-2010, and3001-3010; sourcecontains that node’s stable replica macro.
6. Verify ElasticQuery schema sync and reads
Wait for at least one schema-sync interval, select every ElasticQuery node in
SQL Console, and open database e2e.
The table list must contain:
- one physical reader labelled
Synced from <replica-id>for every ClickHouse node; - one
eventstable labelledAll data.
Query the logical aggregate:
SELECT
count() AS rows,
uniqExact(source) AS source_nodes,
min(id) AS first_id,
max(id) AS last_id
FROM e2e.events;
Expected result:
rows = 30
source_nodes = 3
first_id = 1001
last_id = 3010
Also select each physical reader from the table list and run:
SELECT
count() AS rows,
groupArrayDistinct(source) AS sources
FROM e2e.`events__<replica-id>__<uuid-fragment>`;
Pass criteria: each physical reader returns 10 rows from only its source replica, and every ElasticQuery node returns the same aggregate result.
7. Prove the opt-in boundary and read-only boundary
Create a normal ClickHouse table without table_disk = true:
CREATE TABLE e2e.clickhouse_only ON CLUSTER all_replicas
(
id UInt64,
value String
)
ENGINE = MergeTree
ORDER BY id;
After a schema-sync interval, clickhouse_only must remain available on the
ClickHouse nodes and must not appear in ElasticQuery.
On ElasticQuery, attempt a harmless write to the managed table:
INSERT INTO e2e.events
VALUES (999999, now64(3), 'elasticquery', 'must fail');
Pass criteria: the write is rejected as read-only and the aggregate row count remains 30.
8. Prove drop and recreate convergence
Capture the current table identity on a ClickHouse node:
SELECT uuid
FROM system.tables
WHERE database = 'e2e' AND name = 'events';
Drop the table:
DROP TABLE e2e.events ON CLUSTER all_replicas SYNC;
After schema sync, ElasticQuery must no longer expose the old physical readers
or the events aggregate. Re-run the DDL from step 4 and confirm:
- ClickHouse assigns the recreated table its new identity;
- schema sync creates replacement readers;
- no stale reader remains visible;
- new inserts are visible through the recreated aggregate.
9. Record evidence and clean up
For a release or handoff, save:
make testand Helm lint/render results;- CR YAML with status and conditions;
kubectl get pods --all-namespaces -o wide;- topology query results;
- the three local ClickHouse results;
- ElasticQuery physical-reader and aggregate results;
- the read-only failure;
- the drop/recreate identity and cleanup result.
Clean up the SQL fixture when the environment is no longer needed:
DROP DATABASE IF EXISTS e2e ON CLUSTER all_replicas SYNC;
Service and Keeper deletion are separate lifecycle tests and should be performed through the Console when destructive cleanup is in scope.
Additional manual scenario coverage
Provisioning and identity
Verify the complete user path:
- install the MCS Operator from zero;
- log in as the platform administrator;
- create a Keeper fleet and an organization;
- log in as a user belonging to that organization;
- create, inspect, update, and delete a ClickHouse service through the Console;
- prove that users cannot read or mutate another organization’s resources;
- verify credentials, endpoints, and public documentation access.
ClickHouse and ElasticQuery
For every supported ClickHouse release:
- create a service with multiple ClickHouse nodes;
- create eligible MergeTree-family tables with
table_disk = true; - insert distinct rows through every ClickHouse node;
- query each ClickHouse node and every ElasticQuery node;
- verify
all_shardsandall_replicascluster queries andON CLUSTERDDL; - drop and recreate a table, then verify stale ElasticQuery readers are removed and the new table identity is mounted;
- verify duplicate UUID handling and schema-sync failure reporting;
- confirm ElasticQuery remains read-only and uses virtual-hosted object storage addressing;
- confirm ordinary tables that do not opt into table-disk sharing are not mounted by ElasticQuery.
The assertions must check row identity and count, not only that a query returns HTTP 200.
Keeper topology and disruption
Exercise 3-, 5-, and 7-member Keeper fleets on enough schedulable Kubernetes nodes to satisfy hard anti-affinity.
For each topology:
- verify quorum and membership;
- continuously insert and read ClickHouse data;
- delete followers and the current leader;
- restart and roll Keeper members one at a time;
- inject a pod failure during a rolling update;
- drain or stop a Kubernetes node;
- verify recovery after quorum-safe and quorum-losing failures;
- confirm Keeper-only changes do not roll ClickHouse service pods;
- retain Keeper logs and membership output for every failure.
The expected availability during quorum loss must be explicit. The test must not report success merely because all pods eventually become Ready.
Scaling and lifecycle
Verify:
- ClickHouse scale-out through the supported replica range;
- ElasticQuery scale-up and scale-down;
- scheduler and anti-affinity behavior;
- deletion and recreation of services and Keeper fleets;
- namespace and credential lifecycle;
- cleanup of schema-sync readers and object-storage identities;
- operator restart while resources are reconciling.
Upgrade coverage
Upgrade testing is a separate release gate:
- operator upgrade with existing CRs;
- ClickHouse release upgrade without an unintended downgrade;
- Keeper upgrade without rolling ClickHouse workloads;
- schema-sync compatibility across supported versions;
- rollback behavior when a new operator cannot reconcile a resource.
Upgrade scenarios do not belong in every fast pull request, but must run before promoting a release.
Automation roadmap
Phase 1: build one reusable harness
Create a checked-in end-to-end runner rather than accumulating shell fragments. The runner must provide:
- Kind cluster creation with a configurable worker count;
- Helm installation using versioned test values;
- deterministic bootstrap Secrets and isolated object-storage prefixes;
- admin and organization-user HTTP clients;
- Kubernetes readiness checks with explicit timeouts;
- ClickHouse SQL execution against named nodes;
- assertions for rows, UUIDs, topology, status conditions, and pod revisions;
- fault actions such as pod deletion, rollout, node stop, and operator restart;
- automatic logs, events, manifests, Keeper state, and schema-sync collection;
- idempotent cleanup that never deletes outside the test prefix.
Each action should return structured evidence. A test must fail on a violated assertion instead of relying on an engineer reading Console output.
Phase 2: automate the L3 happy path
Turn the current from-zero runbook into one Kind smoke test:
- create the cluster and install the charts;
- create Keeper and an organization as the administrator;
- create a service as an organization user;
- wait for ClickHouse and ElasticQuery readiness;
- create table-disk tables, insert per node, and verify ClickHouse and ElasticQuery results;
- exercise drop/recreate schema sync;
- delete resources and verify cleanup.
Once reliable, run it for every change to controllers, charts, storage, schema sync, or Console mutations. It can later become a required pull-request check.
Phase 3: automate L4 resilience
Reuse the same harness with a scenario matrix instead of a second test system:
- Keeper sizes 3, 5, and 7;
- leader and follower deletion;
- rolling update with concurrent inserts;
- pod failure during rollout;
- Kubernetes node disruption;
- operator restart during reconciliation;
- ClickHouse and ElasticQuery scaling;
- repeated table drop/recreate.
Run this matrix nightly. Keep a smaller representative fault case available for labeled pull requests.
Phase 4: automate L5 release qualification
Parameterize the harness with source and target versions. Release qualification must create data before an upgrade, perform the operator/Keeper/ClickHouse transition, and verify the same data and control-plane invariants afterward. The version matrix and rollback expectations belong in source control.
Target CI tiers
Pull request
Target: fast and deterministic.
- lint;
- generated artifact verification;
- Go unit and envtest suites;
- Helm lint and render;
- container build smoke test.
Kind smoke
Target: one live happy path for changes that affect controllers, charts, Console mutations, storage, or schema sync.
- install from zero;
- create Keeper, organization, and service through supported APIs/UI;
- run table-disk DDL, insert, ClickHouse query, and ElasticQuery query;
- delete all test resources cleanly.
This can run on labeled pull requests until its duration and reliability are appropriate for every pull request.
Nightly resilience
Target: failure discovery.
- 3/5/7 Keeper topology matrix;
- rolling updates with concurrent inserts;
- leader and pod deletion;
- node disruption;
- service and ElasticQuery scaling;
- repeated drop/recreate schema sync;
- operator restart and reconciliation recovery.
Release qualification
Target: supported-version confidence.
- full from-zero installation;
- supported ClickHouse and Keeper version matrix;
- upgrade and rollback paths;
- multi-architecture image smoke tests;
- retained test report and cluster artifacts.
Test data and cleanup
End-to-end data should be deterministic and large enough to expose correlation and lifecycle errors without becoming a performance benchmark. Every run must use a unique test ID in:
- Kubernetes names and labels;
- namespace names;
- service and replica IDs;
- object-storage prefixes;
- table names or comments when necessary.
Cleanup is part of the test. A successful run verifies that owned Kubernetes resources disappear and that recreated tables no longer expose stale readers. Destructive cleanup of a shared bucket is not permitted.
Failure artifacts
On failure, CI should retain:
- operator and workload logs;
- Kubernetes events;
- CR YAML including status and conditions;
- rendered Helm values and manifests;
- pod placement and rollout history;
- Keeper membership and quorum output;
- schema-sync logs;
- the SQL statements and result assertions used by the test.
Without these artifacts, an intermittent end-to-end failure is not diagnosable.
When a manual test becomes automated
A manual scenario is not considered automated merely because its commands were copied into a script. It becomes automated only when:
- its runner and inputs are checked into the repository;
- environment requirements and Secrets are declared;
- setup has bounded readiness checks and failure timeouts;
- success is decided by machine-readable assertions;
- failure artifacts are collected automatically;
- cleanup is isolated and repeatable;
- it runs in a named CI tier on a documented trigger;
- ownership exists for fixing a consistently failing test.
The next implementation priority is Phase 1 followed by the single L3 happy path. Browser coverage, schema-sync lifecycle, Keeper disruption, and upgrades should then be added to that same harness rather than implemented as unrelated one-off jobs.
New features must state which level proves them and add the smallest automated test that would have caught their most likely regression.