Strimzi KafkaTopic GitOps export¶
Status: implemented and supported only for deterministic offline export of
managed topic artifacts as Strimzi 1.2.0 KafkaTopic YAML. Direct Kubernetes
or Strimzi operations and every broader boundary below remain unsupported.
This specification defines a deterministic, offline export of managed streamt
topic artifacts as Strimzi KafkaTopic custom resources. It defines a GitOps
artifact boundary, not a Kubernetes client or a second deployment-state engine.
Normative language¶
MUST, MUST NOT, SHOULD, and MAY are normative. When an identity,
ownership decision, or topic setting cannot be represented exactly, the command
MUST fail. It MUST NOT normalize away the conflict or silently broaden its
scope.
Pinned target and primary sources¶
The first target is the stable Strimzi Kafka Operator 1.2.0, released on
2026-08-20, at immutable commit
6c7b43c4af0db547c10463ba09d1dfa6f5e156a0.
The emitted resource is exactly kafka.strimzi.io/v1, kind KafkaTopic.
Strimzi 1.2.0 supports only its v1 custom-resource API.
The frozen upstream evidence is:
| Input | Immutable location | SHA-256 |
|---|---|---|
| Release | Strimzi 1.2.0 | tag resolves to 6c7b43c4af0db547c10463ba09d1dfa6f5e156a0 |
| Release archive | strimzi-1.2.0.tar.gz | 94f66d5387e6fe653e71df3520833d63155f81d78c7dc180d9a0656f7e7ddecb |
| Complete CRD bundle | strimzi-crds-1.2.0.yaml | 6f300e77128dc0963b8a6153c520a0f11b6f8999486a9b8e361c06aaa17860a3 |
| Cluster Operator install | strimzi-cluster-operator-1.2.0.yaml | a0b1ae3e375a7da0674eb3894df8aa955bc50abe8190bc9344e30b83bbee4775 |
| KafkaTopic CRD | 043-Crd-kafkatopic.yaml |
36390f0731c699448076d4ee739e8b7f331d083e91a7fb71500aaa830ab1127e |
| KafkaTopic example | examples/topic/kafka-topic.yaml |
58b929f6d1d07e208d99ce44c3884b36e2e8ddd1e8400ae6ca5cf302ae0d352c |
| Single-node example | examples/kafka/kafka-single-node.yaml |
2e7739e13dc250ccd00872bc6acf08dbf7fe768b9b76afcbef0dc733ede7b9ea |
| Ephemeral-storage example | examples/kafka/kafka-ephemeral.yaml |
dd12c1e217e7ff348f5be81f9289a6f8c809db5bf4d5bb6b14e24ef7156d4930 |
The normative behavior is also grounded in the versioned official
topic-management and naming documentation,
the pinned
KafkaTopicSpec,
and the pinned Topic Operator
configValueAsString
conversion. Kafka topic names are checked against Apache Kafka 4.3.1
Topic.validate.
Newer Strimzi, Kubernetes, or Kafka behavior is not evidence for this contract. Any target-version change requires a separate compatibility review, new source digests, byte fixtures, package parity, and real-cluster acceptance.
Scope and non-claims¶
The command emits only KafkaTopic resources for compiled topic artifacts
whose lifecycle ownership is exactly managed. Compiler-generated managed DLQ
topics are included. It does not emit source declarations merely because they
refer to Kafka topics.
The command does not:
- contact Kubernetes, Strimzi, Kafka, DNS, HTTP, sockets, or a subprocess;
- construct a streamt deployer, deployment planner, state service, reviewed operation, or provider client;
- apply, patch, delete, prune, observe, import, adopt, reconcile, or roll back a resource;
- emit
Kafka,KafkaNodePool,KafkaUser,KafkaConnector, Connect, MirrorMaker, Schema Registry, Flink, Gateway, ACL, quota, or secret resources; - accept arbitrary labels, annotations, raw custom-resource fragments, Helm, Kustomize, or templating input;
- infer namespace or cluster identity from bootstrap servers, runtime endpoints, the selected environment, state, or a current Kubernetes context; or
- claim that the resulting stream represents the complete streamt project.
Strimzi documents that creating, changing, or deleting a KafkaTopic causes
the Topic Operator to perform the corresponding Kafka operation. Consequently,
deletion of generated YAML from Git, pruning by a GitOps controller, deletion
of the Kubernetes resource, finalizer behavior, and deletion of the Kafka topic
are explicitly outside this contract. streamt emits no tombstone and grants no
destructive authority by absence.
Command contract¶
streamt export strimzi \
--namespace <KUBERNETES-NAMESPACE> \
--cluster-name <STRIMZI-KAFKA-CLUSTER> \
[--output-file <PATH>] \
[--project-dir <PATH>] \
[--env <ENVIRONMENT>]
--namespace and --cluster-name are required and MUST be validated before
project parsing, compilation, or file creation. Neither value is trimmed,
case-folded, slugged, or inferred.
The command validates those required values itself rather than delegating the
missing-value case to Click, so absence and malformed input use the same
E509_STRIMZI_INVALID boundary defined below.
The namespace MUST be a Kubernetes DNS-1123 label: 1 through 63 lower-case
ASCII alphanumeric or - characters, beginning and ending with an
alphanumeric character. The cluster name MUST satisfy the same rule because it
is emitted as the strimzi.io/cluster label value. This deliberately narrow
boundary avoids depending on a longer Kubernetes object name that cannot be
represented as the required Strimzi label.
The command uses standard project and environment resolution and runs
Compiler.compile(dry_run=True) exactly once. It reads only the resulting
manifest's topic artifacts and the stable manifest_checksum() that excludes
compiled_at. It MUST NOT use the catalog projection: that projection does not
contain partitions, replicas, or topic configuration.
Output modes¶
In text mode without --output-file, stdout is only the canonical YAML stream
and warnings go only to stderr. In text mode with --output-file, the complete
stream is atomically written and stdout is empty. Quiet mode suppresses text
and warnings but not errors or a requested file write. Quiet mode without an
output file is invalid because it has no artifact sink.
Quiet success with an output file emits no stdout or stderr, including when the root output mode is JSON. Quiet failure still emits the ordinary JSON error envelope in JSON mode and the fixed stderr error because quiet never suppresses errors. A non-quiet JSON success emits warnings only in the envelope and keeps stderr empty; a non-quiet text success emits each warning occurrence to stderr in its frozen order.
JSON mode emits one ordinary streamt formatter envelope. Its data contains
exactly target_release, api_version, kind, manifest_checksum,
documents, counts, and nullable output_file; it does not interleave raw
YAML. documents contains defensive JSON-compatible copies in canonical YAML
order. counts contains exactly emitted_topics, external_topics_omitted,
and other_artifacts_omitted. other_artifacts_omitted is the sum of the
lengths of the schemas, flink_jobs, test_jobs, connectors,
connector_removals, gateway_rules, and gateway_rule_removals artifact
collections. A missing additive removal collection has length zero. An
unexpected artifact collection fails closed rather than being silently
omitted.
When present, JSON output_file is the exact lexical argument supplied by the
caller, not a resolved absolute path. Project-parser compatibility warnings are
suppressed at this boundary; only the frozen W120 and W121 mapper warnings can
appear in Strimzi output.
An export with no managed topics succeeds and its canonical YAML is zero bytes.
Topic selection and ownership¶
Each input topic artifact MUST be parsed with a closed, strict parser. The
required fields are name, integer partitions from 1 through 2,147,483,647,
integer replication_factor from 1 through 32,767, object config, and exact
ownership. The partition maximum is the representable maximum of pinned
Strimzi's Java Integer property; the CRD itself specifies only the minimum.
Extra or malformed fields fail the whole export; malformed artifacts MUST NOT
be skipped.
managed: emit exactly oneKafkaTopic.external: emit nothing and produceW120_STRIMZI_EXTERNAL_TOPIC_OMITTEDwith fixed messageExternal topic artifact omitted from Strimzi exportand locationartifacts/topics/<INDEX>/ownership, where<INDEX>is the unpadded, zero-based decimal index in the compiled topic collection. The warning contains no topic or owner name and no configuration value.adopted: fail withE509_STRIMZI_INVALID. An offline artifact cannot prove prior state or that an existing topic is safe for a new Topic Operator to claim.- absent, unknown, mismatched-project, or inconsistent ownership: fail with
E509_STRIMZI_INVALID.
If schemas, Flink jobs, test jobs, connectors, Connector removals, Gateway
rules, or Gateway removal artifacts are present, the command emits
W121_STRIMZI_ARTIFACTS_OMITTED once. Its fixed message is Non-topic
artifacts omitted from Strimzi export. Its exact location is
artifacts/omitted/schemas=<S>,flink_jobs=<F>,test_jobs=<T>,connectors=<C>,connector_removals=<D>,gateway_rules=<G>,gateway_rule_removals=<R>,
where each placeholder is the unpadded decimal collection length. The location
always contains all seven kinds in that order, including zero counts, and
never contains names or artifact contents.
Warnings are immutable and sorted by (location, code). Raw stderr uses fixed
human messages without relying on codes; JSON warning objects contain exact
code, message, and location keys in the ordinary formatter order.
Exact identity and naming¶
Kafka identity is the physical TopicArtifact.name. Under the pinned Kafka
4.3.1 rule it MUST:
- contain 1 through 249 ASCII characters;
- contain only letters, digits,
.,_, and-; and - not equal
.or...
The value is never case-folded or normalized. Duplicate physical topic names fail before serialization.
The Kubernetes metadata.name is deterministic:
- If the Kafka name is also a DNS-1123 label of at most 63 characters,
metadata.nameis the exact Kafka name. - Otherwise it is
streamt-topic-followed by all 64 lower-case hexadecimal characters ofsha256(topic_name.encode("utf-8")).
The full hash MUST NOT be truncated. A collision among generated Kubernetes
names fails. spec.topicName is always present and is always the exact Kafka
name, including when it equals metadata.name. This makes the physical Kafka
identity explicit and immutable; changing it produces a different artifact
rather than an in-place rename claim.
Exact document shape¶
Each document contains only this shape and key order:
The streamt:skip comment is a documentation-test marker and is not part of
the emitted document.
# streamt:skip -- normative third-party Strimzi custom resource shape
apiVersion: kafka.strimzi.io/v1
kind: KafkaTopic
metadata:
name: <deterministic-kubernetes-name>
namespace: <exact-namespace>
labels:
strimzi.io/cluster: <exact-cluster-name>
app.kubernetes.io/managed-by: streamt
annotations:
streamt.dev/manifest-checksum: sha256:<64-lowercase-hex>
streamt.dev/owner-name: <logical-owner-name>
streamt.dev/owner-type: <model-or-source>
streamt.dev/ownership-mode: managed
streamt.dev/project: <exact-project-name>
streamt.dev/strimzi-release: 1.2.0
spec:
topicName: <exact-kafka-topic-name>
partitions: <integer-1-through-2147483647>
replicas: <integer-1-through-32767>
config: {}
The lifecycle owner type MUST be exactly model or source; a source owner is
expected for a compiler-generated managed DLQ attached to a source test. Any
other owner type fails. All annotation keys and values are strings. Project and
logical owner annotation values reject NUL, Unicode control characters,
surrogate code points, and the exact code points U+FEFF, U+FFFE, U+FFFF, and
U+10FFFF that the pinned canonical YAML emitter would backslash-escape; accepted
values are otherwise exact. The complete annotation map MUST remain within the
Kubernetes 256 KiB annotation limit. config is always
present; an empty map is emitted exactly as config: {}. No field in the shape
above is omitted. Documents are sorted by
(spec.topicName, metadata.name) using Unicode code point order.
The checksum annotation binds every document to the current
manifest_checksum() secret-neutral canonical projection of the entire
successfully compiled manifest rather than only the represented topic. That
projection excludes compiled_at and replaces values whose keys match the
repository sensitive-key policy with the fixed "<redacted>" marker. Changes
to represented non-secret manifest content therefore change the bytes even
when the KafkaTopic specs do not; changes only to redacted secret values do
not. The checksum is evidence of this secret-neutral compiler projection, not
the original secret-bearing input and not proof of review, approval, live
absence, or safe application.
Topic configuration scalar boundary¶
Configuration keys MUST be non-empty, single-line ASCII strings, MUST NOT
contain control characters, and MUST NOT match this exact case-insensitive
sensitive-key expression, which is the repository policy at this proposal's
freeze point:
(^|[._-])(?:password|passwd|secret|token|api[_-]?key|authorization|credentials?|basic[._-]auth[._-]user[._-]info|sasl[._-]jaas[._-]config)($|[._-]).
This rejects both credential and credentials, and apikey, api_key, and
api-key, at dot, underscore, or hyphen boundaries.
Only exact Python str, bool, and non-boolean int values are accepted.
Lists, mappings, nulls, floats, non-finite values, bytes, secret wrappers, and
all other types fail without echoing the value. Although pinned Strimzi also
converts numeric, boolean, and list values, this first slice deliberately
excludes lists and floating-point spellings.
All accepted values are emitted as YAML strings because Kafka configuration is textual at the Topic Operator boundary:
- strings are preserved byte-for-byte after rejecting NUL, Unicode control characters, surrogate code points, and U+FEFF, U+FFFE, U+FFFF, and U+10FFFF, which the pinned canonical YAML emitter would otherwise backslash-escape;
- booleans become lower-case
"true"or"false"; and - integers become their base-10 representation without a leading
+.
Configuration keys are sorted lexicographically. Parsing the emitted YAML MUST recover every configuration value as a string.
Canonical YAML and atomic files¶
Serialization is UTF-8, LF-only, Unicode-unescaped, block-style YAML with no
aliases, anchors, explicit tags, end markers, comments, timestamps, or
environment-dependent values. Every non-empty document begins with ---; the
stream has one final newline and no trailing .... Mapping order is exactly
the order specified above, with sorted configuration keys.
Strings matching the frozen conservative quoting predicate derived from the
Kubernetes YAML conversion stack's
go-yaml v2 resolver
MUST use single-quoted scalar style. The predicate contains the resolver's
exact boolean, null, merge-key, infinity, and NaN words. It also removes every
underscore and quotes lexical decimal/scientific-number forms, base-prefixed
integer forms, and values with a timestamp-shaped YYYY-M-D prefix. This
includes exponent-only floats such as 1e3, YAML 1.2 octal values such as
0o7, and ambiguous mapping keys. It is intentionally a conservative lexical
superset: values such as _1, invalid timestamp-shaped strings, or numeric
overflow strings MAY be quoted even when the resolver would retain a string.
The quoting preserves the exact string type and value; it is not normalization.
Strings outside this frozen predicate retain the pinned emitter's ordinary
style, so the rule does not change the reviewed fixture bytes.
No accepted scalar is serialized with a backslash-u or backslash-U Unicode escape. The narrow four-code-point exclusion above is part of the canonical transport contract; it does not normalize or rewrite accepted input.
The complete document tuple MUST pass strict local validation before any byte
is written. For --output-file, serialization completes in memory, then a
same-directory private temporary file is written, flushed, fsynced, and
atomically replaced. Failure preserves an existing target and removes every
staging file. Symlink and non-regular-file targets fail closed.
Missing parent directories are created before staging. The destination is
checked with lstat before staging and again immediately before replacement;
directories, FIFOs, sockets, devices, symlinks, and a destination swapped to
one of those types by either observation all fail. The randomized staging file
is in the destination directory with mode 0600, and its identity is likewise
checked against the open descriptor immediately before replacement. Cleanup
covers descriptor creation, wrapping, write, flush, fsync, close, and
replacement failures, including BaseException, while command-level error
conversion does not catch BaseException.
The output directory is a caller-controlled trust boundary. As with portable
same-directory temporary-file writers generally, mutation by another actor
with write access after either final lstat sample and before os.replace
cannot be distinguished atomically on every supported platform. os.replace
does not follow a destination symlink, so a post-sample destination swap cannot
write through to its referent; nevertheless, callers MUST NOT select a
directory writable by an untrusted actor. Tests cover every mutation observable
at the frozen checks and prove that replacement never follows a destination
symlink.
Secret-neutral failure boundary¶
The exporter never reads runtime endpoints or constructs provider/state objects. It MUST NOT expose project runtime secrets, connection configuration, connector configuration, SQL, environment-variable values, Python exception text, temporary paths, or rejected configuration values in output, warnings, errors, logs, or object representations.
All failures contained after construction of the export formatter use
E509_STRIMZI_INVALID with the exact message Strimzi export failed safely
and one safe structural location. Click's argument-tokenization errors (for
example, an option token with no following value or an unknown option) and a
failure to construct the formatter itself remain framework/bootstrap failures
outside this boundary. Missing or domain-invalid namespace and cluster-name
values are parsed successfully and MUST use the E509 boundary. The location for
a contained failure is selected from this closed table:
| Location | Failure phase |
|---|---|
target.namespace |
missing or invalid namespace |
target.cluster_name |
missing or invalid cluster name |
output |
quiet without an output-file sink or another output-mode invariant |
project |
project path resolution, parsing, environment selection, or validation |
manifest |
dry-run compilation |
manifest_checksum |
whole-manifest identity calculation |
| a mapper location from its closed allowlist | strict artifact mapping or document validation |
output_file |
parent creation, destination validation, staging, write, sync, close, or replace |
stdout |
final raw-text or JSON-envelope write/flush |
export |
unexpected ordinary-exception containment fallback |
The mapper allowlist is exactly project, manifest_checksum, target,
artifacts, artifacts/<KIND> for one of the eight frozen artifact collection
keys, documents, and the result-factory locations export.manifest_checksum,
export.counts, export.documents, export.warnings, and export.yaml. Any
other mapper location becomes export.
The command catches its named parse, validation, compile, identity, mapper,
schema, YAML, and I/O failures at the phase locations above. A final
except Exception containment guard maps every remaining ordinary exception to
export without exposing its text; it deliberately does not catch
BaseException. Before emitting an error it clears any materialized data,
warnings, and prior errors, so an error-envelope retry cannot include material
that stdout has not already accepted. Parser and compiler logging is suppressed
for this command even under root --verbose.
Stdout is a non-transactional boundary. A write can accept some or all bytes
and then fail during that write or its flush; accepted bytes cannot be
retracted. When the stream is known to be untouched, JSON mode retries with the
fixed E509 envelope. Once a write may have begun, the command exits 1 and
prints the fixed E509 message to stderr but MUST NOT append a second JSON
envelope to the possibly partial or complete first one. Raw text has the same
irretractable-write limitation. This exception does not permit confidential
data on the success surface: only the already-frozen public document and
defensive result fields can have been accepted before the transport failure.
Tests MUST seed distinct confidential sentinels in Kafka runtime endpoints, Schema Registry, Flink, Connect, Gateway, state, connection configuration, SQL, omitted tags, and rejected topic-config values and prove that none reaches success or failure surfaces. Separate public identity sentinels MUST be used for project, physical topic, and logical owner names. For managed topics those values MUST appear only in their exact allowlisted document fields and JSON defensive copies; they MUST remain absent from warnings, errors, logs, exception text, and unrelated fields. An omitted external topic's public identities MUST not appear because it has no emitted document. Expected public identities in those allowlisted fields are not classified as secrets.
A fresh import of streamt.cli and of its Strimzi export command module MUST
not import deployers, the deployment planner, providers, state backends, state
services, network clients, or subprocess helpers. Command invocation may load
streamt.core.runtime and streamt.core.deployment_state only as project
configuration models needed by the existing parser/compiler; it MUST NOT
construct or import deployment behavior from those layers.
Validation evidence levels¶
The following evidence MUST remain distinct:
- Closed local validation checks the exact streamt document shape, ownership, identities, scalar normalization, cardinality, sorting, and canonical bytes.
- Pinned CRD validation validates each document against the
v1openAPIV3Schemaextracted from the exact bundled KafkaTopic CRD whose hash is recorded above. Kubernetes extensions are handled explicitly. Because the CRD preserves unknownconfigfields and leaves metadata broadly typed, this is not a substitute for the closed validator. - Kubernetes API acceptance performs server-side dry-run against an ephemeral API server with the exact CRD installed.
- Real-cluster acceptance applies the installed-wheel output to the pinned
Strimzi Topic Operator and Kafka cluster, waits for
Ready=True, and proves exact custom-resource and broker read-back plus idempotent replay. Kafka identity metadata MUST validate before polling state is classified. An absent or null initial.statusis pending, a present non-mapping status is invalid, and a structurally valid but not-yet-observed or not-yet-Ready status remains pending.
Passing levels 1 and 2 supports only the claim “offline validated Strimzi 1.2.0 KafkaTopic output.” A working GitOps integration claim requires all four. None of the levels supports production Kubernetes apply, credentials, TLS, multi-namespace operation, deletion, reconciliation ownership transfer, or upgrade safety.
Release boundary¶
Source, installed-wheel, and installed-sdist exports MUST be byte-identical on Python 3.10 through 3.14. Separate wheel and sdist installations run in clean environments outside the checkout. The smoke harness may start each installed executable, but the exporter process is guarded against network, DNS, HTTP, socket, and child-subprocess use and against provider, deployer, deployment-planner, and deployment-state construction. The wheel and sdist MUST contain the pinned schema and notices, contain no Kubernetes or Strimzi Python SDK, and add no runtime dependency or extra.
The real gate uses installed streamt output generated before any cluster mutation. It is test-only and pinned to:
- kind
v0.33.0, annotated tag object49aeee6b958d818ae881752fe5b09220b39b6f55, peeled to source commit407a9675e6d9af1200b5f57f9ca52ec6cdacce74; kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0;- Strimzi operator
quay.io/strimzi/operator@sha256:77f8fa8121a67561c3418de985783d197f51b8931e9a47f793dc0437dc6bb21f; - Kafka 4.3.1
quay.io/strimzi/kafka@sha256:fef34b5438e8556cc08c01f3e254e47346f061b53a4e38d4289853777e0ea7f1; and kubectlv1.35.8, whose official Linux amd64, Linux arm64, and Darwin arm64 SHA-256 values are respectively874d5e72dbb819f43cff16bcd1e4f8bac5b7f2361fe1e55049b0a6c676fb0cbf,cc749967b62f4422260bc9c0aa7a7c55f45175ae38cb8d95767b5d2b7e04c1fd, andb8be50ae0c6665b646fb009f904a52cad30806deee19ab3b4fe5af2d68bd82eb.
The official kind v0.33.0 Darwin arm64 binary used by the reviewed local pilot
has SHA-256
0c8c7dbe5e23594a198b786c4bc13dacc101fa6196b0cb0b23a1ca44e61f4b4f.
Host-tool selection and container-image selection are separate: the local
pilot uses Darwin arm64 kind/kubectl binaries with Linux arm64 image children,
while the release lane uses Linux amd64 for both. The gate MUST derive the
image platform from the Docker server, not from the host Python platform.
Locked GitHub release URLs may follow exactly one HTTPS redirect from
github.com to release-assets.githubusercontent.com; the signed destination
query is transport metadata and is not a provenance identity. Every other
redirect source, target, or count fails. Raw GitHub and dl.k8s.io inputs are
required to remain direct. In every case the bounded downloaded bytes MUST
match the frozen SHA-256 before use.
The reviewed 27-document operator asset contains exactly three Kafka image-map
environment variables: STRIMZI_KAFKA_IMAGES,
STRIMZI_KAFKA_CONNECT_IMAGES, and
STRIMZI_KAFKA_MIRROR_MAKER_2_IMAGES. Each source value MUST be a canonical,
newline-terminated map with the exact ordered keys 4.2.0, 4.2.1, 4.3.0,
and 4.3.1; the operator contract freezes both that ordered key list and the
complete source-value SHA-256. The structural rewrite MUST retain all four keys
in that order and map every key to the same selected-platform pinned Kafka
4.3.1 child reference. Missing, extra, duplicate, reordered, malformed,
indirect, or mutable source or rewritten entries fail closed. Versions 4.2.0,
4.2.1, and 4.3.0 are startup-compatibility aliases required by Strimzi's full
lookup, not supported test workloads: the Kafka custom resource MUST still
select exactly 4.3.1, pull policy remains Never, and workload image closure
permits only the pinned child.
The node, operator, and Kafka digests above are immutable multi-platform index identities. The gate's image lock MUST additionally freeze, for each supported Linux runner architecture, the selected child-manifest digest and that manifest's config digest:
| Image | Linux amd64 manifest / config | Linux arm64 manifest / config |
|---|---|---|
| kind node | sha256:0c58cebbb66d7fa5fd497235dfae1e4e722ff84104e24a6f736ce8cd607cbe7c / sha256:194068f84949f79dca8527c1e0578d9cd90f0bcd82a359bdb0d2d5bfe9d61185 |
sha256:b38a25576c835bfedc9d06368f87ec40863459a4d5dcbdbab2fd5f58ecf97466 / sha256:664b3989afaffcd2268ece28d6cf012b27700e6b8e81c3c7641cc167889075f5 |
| Strimzi operator | sha256:6df3bf9f92d3d1907aca08ade8c6df6cdacd2e235756afad419ad582ce6a2c4e / sha256:307ebd6e0fd9121e0775b1cf0f06a5658cece38c58d46082512b910a7d095ce3 |
sha256:ee8d9fb08ede3778120c33c42c70da16762b531d70e32790b9e2ff932e040927 / sha256:693db9e33a50f7cc1cd84cb763ee083c5209412b16f9f198ca013546da44f4f1 |
| Kafka 4.3.1 | sha256:1699c345852618c02ed58a168923871ad3a4d9012e4181ecaa138c9bc55a8b6d / sha256:ba984c01faaf5b9d9ccc2aeba9ec7e2177a970caec767dfa477b8d8a94df98f3 |
sha256:ffba1669b6daa7e186a17b0c49b48f4dfd8ef5872720e0eec9bf7c4612dd1bcb / sha256:5f6ad7b02f27af240676afddbe36b63c419bc4cdfcf1b012db989b6d4fc4f684 |
The previous test lock labeled Strimzi's official
1.2.0-kafka-4.3.0 image chain as Kafka 4.3.1. The corrected lock replaces
that index and both platform children/configs in full; no identity from the
mislabeled 4.3.0 chain remains eligible. After the Kafka resource is Ready and
the complete namespace workload/exposure/image closure passes, the gate MUST
execute /opt/kafka/bin/kafka-topics.sh --version in the exact selected
kafka broker container. Before any KafkaTopic dry-run or apply, that bounded
command MUST return exit zero, exact stdout 4.3.1\n, and empty stderr. Its
exact stdout is retained as secret-scanned kafka-version.txt evidence.
Index digests remain the release-provenance roots. The gate MUST resolve the
runner platform through every frozen index. Docker MUST pull and create the
kind node from the selected node child and verify its frozen config identity.
The gate MUST inspect each selected child manifest directly and require its
config.digest to equal the frozen config digest; Docker's local image Id
is not, by itself, config-digest evidence. Local inspection accepts only the
two reviewed backend representations: the classic image store reports
Id=<config-digest> with no descriptor, while Docker's containerd image store
reports Id=<child-manifest-digest> and
Descriptor.digest=<child-manifest-digest>. Both forms MUST also report the
exact singleton child RepoDigests entry and the selected Linux OS and
architecture. The created kind node's Config.Image MUST be that exact child
reference, and its resolved Image MUST equal the corresponding frozen config
or child digest according to those same two representations.
The gate MUST pull and load the selected operator and Kafka children into the
node. It MUST sample bounded exact ctr --namespace=k8s.io images list -q
output immediately before and after each load. A classic load delta is exactly
the Quay child reference plus the frozen config-digest pseudo-reference. A
Docker Desktop/containerd load delta is exactly the config pseudo-reference
plus two bare, same-date, calendar-valid
import-YYYY-MM-DD@sha256:<digest> references: one for the selected child and
one distinct outer digest. Linux OCI conversion, as observed in evidence run
33899306524, has the same exact three-name delta but neither import digest is
the selected child. Operator and Kafka MUST use the same one of the three
representations (classic, desktop, or oci-converted). Import sources MUST
be disjoint across loads. Each current inner/outer digest MUST be disjoint from
every prior inner/outer and layer digest, and each current layer digest MUST be
disjoint from prior inner/outer digests. All current transformed and layer
digests MUST also be disjoint from the selected node, operator, and Kafka
manifest/config identities. Layer digests MAY repeat across operator and Kafka:
the pinned images on both supported Linux platforms legitimately share
immutable rootfs layers.
Before classifying either load, the gate MUST write its already validated,
lexicographically sorted before and after inventories as
ctr-load-<I>-before.txt and ctr-load-<I>-after.txt, where <I> is exactly
0 for operator and 1 for Kafka. From the exact set difference it selects at
most two names accepted by the strict, calendar-valid bare
import-YYYY-MM-DD@sha256:<digest> grammar, sorts them lexicographically, and
reads each digest with the existing bounded ctr content get command. Exit
status MUST be zero and stderr empty. Each result MUST be a bounded JSON object
parsed with duplicate-key rejection and is written as
ctr-load-<I>-import-<J>.json, with <J> assigned by that sorted order. The
canonical artifact has exactly source, raw_content_sha256, and content:
the first is the import name, the second is the SHA-256 identity of the exact
raw bytes, and the third is the parsed JSON value. It contains no base64 or
opaque raw-content copy. Invalid JSON, duplicate keys, a secret match, or an
evidence write failure rejects the candidate evidence set and produces only
the fixed marker at staging. A nonzero result, nonempty stderr, runner
exception, subprocess timeout, or exhausted global deadline during any
selected import's read has the same marker-only result; a partial import
diagnostic set is never staged. JSON NaN, Infinity, and -Infinity are
invalid at this and every other gate JSON input boundary. These files are both
staged evidence and the retained, validated inputs to the explicit three-way
load classifier; they do not authorize any fourth or partial representation.
An OCI-converted pair is accepted only when both source suffixes equal the SHA-256
of their exact raw content and the contents are exactly one inner OCI manifest
and one outer OCI index. The inner object has only schemaVersion,
mediaType, config, and layers: schema version 2, OCI manifest media type,
a closed OCI config descriptor containing the exact frozen config digest and
positive non-boolean size, and a nonempty ordered layer list. Every layer is a
closed descriptor with OCI uncompressed-layer media type, a unique valid
SHA-256 digest, and positive non-boolean size. The outer object has only
schemaVersion, mediaType, and manifests: schema version 2, OCI index media
type, and one closed OCI-manifest descriptor pointing to the inner raw digest
with size exactly equal to the inner raw byte length. Extra annotations or
fields fail closed.
For OCI conversion the gate also reads the frozen config digest with bounded
ctr content get, requires zero status, empty stderr, and raw SHA-256 equality,
and writes canonical ctr-load-<I>-config.json evidence. The parsed config may
contain normal image-config root fields, but rootfs MUST exist and contain
exactly {type: "layers", diff_ids: [...]}. Its ordered, valid SHA-256
diff_ids MUST equal the ordered inner layer digests, and the inner config
descriptor size MUST equal the config raw byte length. Config evidence obeys
the same marker-only parse/secret/write failure boundary.
For the Desktop form, the gate MUST additionally validate its previously
reviewed exact-child outer shape by reading the exact outer content by digest.
The raw content SHA-256 MUST equal that outer digest and decode as a closed OCI
image index with schema version 2 and exactly one Docker schema-2 manifest
descriptor. That descriptor MUST identify the frozen child, have a positive
integer size, and contain exactly the locked Quay source annotation for
strimzi/operator or strimzi/kafka. For either imported form, the gate MUST
tag the validated inner import to the exact Quay child, re-enumerate the names,
remove only the two discovered import
references with ctr images rm (never --sync or content deletion), and prove
the resulting relevant name set is exactly the prior inventory plus the target
and frozen config pseudo-reference. The pre-removal recheck closes accidental
target expansion; its narrow observation-to-removal race is excluded by the
gate's exclusive ownership of the unique, disposable, pre-workload node.
If and only if both loads used the same imported form (desktop or
oci-converted), the gate MUST restart containerd exactly once, wait boundedly
for that exact Kubernetes Node to be
Ready, and repeat Docker attachment, loopback-port, empty IPv4/IPv6 default
route, positive local TCP, and negative external TCP controls. It MUST then
prove the normalized ctr -q inventory did not change. The final CRI record
for each selected image MUST have no repo tags, contain only the exact Quay
child repo digest, and map it to the frozen config identity. The all-classic
path performs no tag, removal, or restart. Missing, extra, partial, mixed,
ambiguous, or differently shaped identities fail before any Strimzi object is
applied. The gate then uses those child references in applied workloads.
For every selected workload container and init container, spec.image MUST be
the exact pinned child reference. Kubernetes status.*ContainerStatuses[].image
is a backend display field and may be only that same child reference or its own
frozen bare config digest; an index, manifest digest, other image's config,
tag, absent value, or non-string fails closed. The separate imageID field is
the runtime identity: pilot mode records one exact consistent value per child,
and normal mode requires exact equality to that reviewed lock. Accepting the
config form in status.image does not relax or substitute the imageID check.
Raw Kubernetes 1.35 pod and service collection reads MUST use the exact generic
apiVersion: v1, kind: List envelope with root keys limited to
apiVersion, kind, metadata, and items, and metadata exactly
{resourceVersion: ""}. Every item MUST identify itself as v1 and exactly
Pod or Service for the requested collection. The gate may normalize a
validated pod collection to an internal PodList; raw typed PodList and
ServiceList envelopes, mixed item kinds, and malformed identities fail
closed.
Before the real lane is enabled, a reviewed pilot MUST record the exact
Kubernetes imageID representation produced by the pinned node/runtime
combination; subsequent runs compare exact values from the image lock rather
than accepting an arbitrary index, child, or config form. Hosted Linux amd64
pilot run 33908141332 recorded the operator and Kafka child references
respectively as
quay.io/strimzi/operator@sha256:6df3bf9f92d3d1907aca08ade8c6df6cdacd2e235756afad419ad582ce6a2c4e
and
quay.io/strimzi/kafka@sha256:1699c345852618c02ed58a168923871ad3a4d9012e4181ecaa138c9bc55a8b6d;
those exact values are now frozen for Linux amd64. This pilot is discovery
evidence only. Permanent normal-mode
CI run 33909664040, job 101143523418
then passed the complete installed-wheel reconciliation, read-back, replay,
isolation, evidence, and zero-residue cleanup contract. That success supports
only the offline managed-topic export stated above; it does not turn the
test-only cluster orchestration into product behavior.
The test-only gate exposes pilot mode only while the selected operator and
Kafka imageID locks are null. A pilot runs the full reconciliation and replay
flow, proves exact applied image references and CRI content first, records one
consistent observed ID per image in secret-scanned evidence, cleans up, and
returns a distinct unsuccessful result. It is discovery evidence, never an
acceptance pass. Normal mode rejects a null lock before mutation, pilot mode
rejects an already frozen lock, and the permanent CI lane MUST use normal mode.
The disposable kind topology uses one uniquely named Docker bridge network
created with the exact option
com.docker.network.bridge.enable_ip_masquerade=false. It is deliberately not
a Docker --internal network: Docker Desktop suppresses the required
loopback-published API binding on an internal network, and kind cannot finish
bootstrapping DNS there because the node starts without a default route. The
gate MUST require Driver=bridge, Internal=false, and exactly one of two
reviewed option representations: the classic singleton containing only
com.docker.network.bridge.enable_ip_masquerade=false, or Docker
Desktop/containerd's map that additionally reports
com.docker.network.enable_ipv4=true and
com.docker.network.enable_ipv6=false. Partial maps, wrong values, and unknown
options fail closed. The gate also requires no
attachment before kind and exactly the derived control-plane attachment after
kind. Immediately after kind becomes ready, the gate deletes the node's
IPv4 default route, requires both ip -4 route show default and
ip -6 route show default to return exact empty output, and only then proves a
positive TCP connection to the node-local API on 127.0.0.1:6443 and a failed
connection to literal external 1.1.1.1:443. The attachment, sole loopback API
publication, and empty dual-stack default-route inventories are fail-closed.
The gate repeats the network-attachment and dual-stack route checks after topic
replay, before declaring success. No operator or Kafka object may be applied
until the first isolation proof passes.
The gate has an internal deadline shorter than its CI job timeout so it can run
bounded evidence capture and exact cleanup/residue checks before failure-only
artifact staging and upload. Evidence candidates MUST remain outside a fresh
upload directory. The exact evidence inventory includes one
ctr-load-<I>-{before,after}.txt pair per attempted load (four files on the
complete two-load path), zero to two conditional sorted
ctr-load-<I>-import-<J>.json files per attempted load, one conditional
ctr-load-<I>-config.json for each OCI-converted load, and ctr-images.txt, a
bounded, secret-scanned, lexicographically sorted canonicalization of
docker exec <exact-node> ctr --namespace=k8s.io images list -q; capture MUST
attempt it on success and failure paths, including when the node or kubeconfig
is absent. It accepts at most 4,096 LF-delimited references of at most 512
printable ASCII bytes each, rejects control bytes, URI schemes, credentials,
queries, fragments, and backslashes, and requires every @ suffix to be an
exact lowercase SHA-256 digest. Syntactically safe unexpected references remain
in the sorted diagnostic inventory rather than being identity-allowlisted. A
nonzero command or zero exit with nonempty stderr produces only the fixed
neutral failure record; stderr is never published. There is no partial
redaction: an unsafe reference, parse failure, secret match, or evidence write
failure rejects the candidate set and yields marker-only staging. An ordinary
evidence command that returns nonzero is represented at
its original filename by fixed JSON containing only its integer return code and
capture-failed status; a runner exception or timeout is represented by a
fixed <filename>.failed artifact with no raw diagnostic. Either case remains
a complete, safe capture and does not by itself force marker-only staging. Only
bounded files that pass a complete secret scan may be copied into the upload
directory. If the capture deadline prevents representing every scheduled item,
or on a secret match, size violation, or candidate scan/read/write failure, no
candidate file may be staged; the upload directory contains only a fixed
secret-neutral scan-failure marker. Cleanup is enforced for success, ordinary
failure, and internal timeout. Hosted-runner cancellation or an external
SIGKILL can prevent process-level cleanup, so that case is best-effort and
relies on runner disposal; it MUST NOT be described as a guaranteed trap.
On every path that reaches the successful runtime-version proof, the full
evidence inventory also contains kafka-version.txt with exactly 4.3.1\n;
the marker-only staging boundary still applies if a later evidence-safety
failure occurs. A version probe failure is recorded by the fixed
summary/capture failure surfaces and cannot cross the KafkaTopic mutation
boundary.
The pinned Strimzi support matrix includes Kubernetes 1.35 and Kafka 4.3.1.
The test cluster is single-node, KRaft, ephemeral, loopback-published and
route-sealed from external networks, and contains no production data. Kafka
process requests and limits are declared on the KafkaNodePool, as required
by the pinned v1 schema; Topic Operator
requests and limits are declared on Kafka.spec.entityOperator.topicOperator.
It validates interoperability; it is not a production deployment
recommendation.