Skip to content

✨ feat(dist): hub distribution — OperatorHub + Artifact Hub (ADR-0708) - #284

Merged
konih merged 17 commits into
mainfrom
dist/hub-distribution
Aug 8, 2026
Merged

✨ feat(dist): hub distribution — OperatorHub + Artifact Hub (ADR-0708)#284
konih merged 17 commits into
mainfrom
dist/hub-distribution

Conversation

@konih

@konih konih commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lands the ADR-0708 hub-distribution lane:

  • Artifact Hub: enrich chart with operator metadata and push Artifact Hub repo metadata on release
  • OLM / OperatorHub: generate an OLM bundle and automate OperatorHub bundle PRs from the release pipeline
  • Docs: ADR-0708 indexed, install guide describes both hub distribution paths
  • Gates: ADR index gate script + dist meta-tests (hack/test/dist_*_test.sh) wired into CI lint

Review round 2 — REQUEST_CHANGES fixes

ID Fix
D1 setup-oras + Push Artifact Hub metadata moved after Publish GitHub Release and marked continue-on-error: true, matching ADR-0708's "soft-fail hub jobs preserve tag-release success". Soft-fail is no longer silent: new reporting steps read steps.<id>.outcome (not .conclusion, which continue-on-error pins to success) and emit a ::warning:: annotation plus a $GITHUB_STEP_SUMMARY line. Same treatment applied to the already-soft-fail OperatorHub submission step; the reporting steps are themselves continue-on-error so nothing after the signed publish can abort the job.
D2 dist_artifacthub_chart_test.sh now parses appVersion and the artifacthub.io/images entry from the same Chart.yaml with yq and fails unless the tag equals v<appVersion>. docs/RELEASE.md step 3 now tells the maintainer to bump the annotation alongside version/appVersion.
D3 operatorhub-pr job now declares environment: release.
D4 operatorhub-pr now needs: [eligibility, release] and checks out ${{ needs.eligibility.outputs.sha }} instead of the mutable tag ref.
D5 dist_olm_bundle_test.sh gained an RBAC drift gate comparing the generated bundle CSV clusterPermissions against config/rbac/role.yaml (order-insensitive, with non-empty guards on both sides so a mistyped path cannot pass vacuously).
D6 dist_artifacthub_release_test.sh no longer requires the placeholder repositoryID (which blocked its own remediation); it asserts a well-formed UUID. The pending-registration fact is re-documented as a dated marker in artifacthub-repo.yml.

Every new assertion was red-stepped: the version-mismatch, step-ordering, missing continue-on-error, mutable-tag-ref, missing-environment, RBAC-verb-drift and malformed-UUID perturbations each fail the corresponding gate, and all revert clean.

ADR-0708's contract was updated to match: protected-environment gate, eligibility-proven SHA checkout, RBAC drift gate, and "hub steps run last and report failures visibly".

D7–D12 from the review were left as operator notes, per the review's own scoping. Also deliberately not gated: the CSV's namespaced permissions: block (leader election) against config/rbac/leader_election_role.yaml — D5 scoped the drift gate to clusterPermissions vs config/rbac/role.yaml, which is where a +kubebuilder:rbac change actually lands.

⚠️ Operator actions required before the next tag

  1. OPERATORHUB_PAT must be moved from a repository secret to a release environment secret. D3 puts the operatorhub-pr job behind environment: release; a repo-level secret is not visible to an environment-scoped job, so the step would see an empty token and self-skip ("OPERATORHUB_PAT not configured; skipping OperatorHub submission"). This cannot be done from a PR.
  2. Any required reviewers / wait timer on the release environment now also gate operatorhub-pr. That is the intended trade for not exposing a cross-repo write credential, but it means a second approval prompt per release. If that is unwanted, create a dedicated operatorhub environment with the PAT and no reviewers instead.
  3. Artifact Hub registration is still pending. artifacthub-repo.yml carries the placeholder repositoryID with a dated marker; paste the real ID from the Artifact Hub control panel after registering the chart repository. CI accepts any well-formed UUID, so this no longer turns the build red.

Copilot AI lite review requested due to automatic review settings August 6, 2026 14:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements ADR-0708’s “hub distribution” lane so Kollect remains primarily installable via Helm OCI on GHCR while also becoming discoverable via Artifact Hub and distributable via OperatorHub/OLM through release automation and CI meta-tests.

Changes:

  • Adds Artifact Hub metadata plumbing (chart annotations + release-time oras push of artifacthub-repo.yml).
  • Introduces OLM bundle templating + a make generate-olm-bundle generator, plus automated dual-upstream OperatorHub PR creation in the release workflow.
  • Adds ADR/docs updates and CI/doc verification meta-tests to gate the distribution wiring.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Mentions the new hub distribution paths while keeping GHCR Helm OCI as primary.
Makefile Adds generate-olm-bundle target to render a registry+v1 OLM bundle from templates + CRDs.
hack/test/dist_operatorhub_pr_test.sh Meta-test to validate OperatorHub PR automation supports dual upstreams and DRY_RUN.
hack/test/dist_olm_bundle_test.sh Meta-test to validate generate-olm-bundle output completeness and expected CSV content.
hack/test/dist_install_docs_test.sh Meta-test ensuring docs mention hubs but avoid premature live badge/listing URLs.
hack/test/dist_ci_wiring_test.sh Meta-test ensuring dist_* scripts run in CI before the generic lint step.
hack/test/dist_artifacthub_release_test.sh Meta-test validating release workflow pushes Artifact Hub metadata and preserves DR-FIND-07 guard.
hack/test/dist_artifacthub_chart_test.sh Meta-test validating required Artifact Hub annotations and CRD listing in Chart.yaml.
hack/test/dist_adr_0708_indexed_test.sh Meta-test verifying ADR-0708 exists and is indexed under theme 07.
hack/operatorhub-pr.sh New script to generate/verify bundles and open/update PRs in OperatorHub upstream repos.
hack/docs/verify.sh Wires ADR/docs distribution checks into docs verification.
docs/getting-started/install.md Documents discoverability paths and clarifies hub listings/badges are omitted until live.
docs/adr/README.md Indexes ADR-0708 under theme 07.
docs/adr/0708-operator-distribution-hubs.md Adds ADR defining the Artifact Hub + OperatorHub distribution approach and constraints.
config/olm/template/metadata/annotations.yaml Adds OLM bundle annotations template (package/channels/mediatype).
config/olm/template/manifests/kollect.clusterserviceversion.yaml Adds CSV template including RBAC, deployment, owned CRDs, and digest-pinned image placeholders.
config/olm/ci.yaml Adds updateGraph config used by OperatorHub repos.
charts/kollect/Chart.yaml Adds Artifact Hub operator metadata annotations (CRDs/links/images/etc.).
artifacthub-repo.yml Adds Artifact Hub repo metadata file (placeholder repositoryID).
.github/workflows/release.yaml Pushes Artifact Hub metadata via oras; adds OperatorHub PR job using generated OLM bundle.
.github/workflows/ci.yaml Runs hub distribution meta-tests (hack/test/dist_*_test.sh) before lint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile
echo "Generating OLM bundle for version $$VERSION (digest: $$IMAGE_DIGEST)..." && \
mkdir -p "$$BUNDLE_DIR/manifests" "$$BUNDLE_DIR/metadata" && \
ICON_B64=$$(base64 < docs/assets/logo.svg | tr -d '\n') && \
sed "s/__VERSION__/$$VERSION/g; s/__DATE__/$$DATE/g; s/__ICON_BASE64__/$$ICON_B64/g; s|__IMAGE_DIGEST__|$$IMAGE_DIGEST|g" \
Comment thread hack/docs/verify.sh
Comment on lines 51 to +53
printf 'docs verify: all available checks passed\n'
bash hack/test/dist_adr_0708_indexed_test.sh
bash hack/test/dist_install_docs_test.sh
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@konih

konih commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

REVIEW — Kollect/dist-hub-distribution @ 1e635e9 — REQUEST CHANGES — 2026-08-07

Verdict: REQUEST CHANGES

Intent (orient): wire Kollect for discoverability on Artifact Hub and OperatorHub per ADR-0708 — enrich Chart.yaml annotations, push artifacthub-repo.yml as :artifacthub.io via oras, generate a hand-templated registry+v1 OLM bundle at release, and open automated PRs to the two community-operators catalogs — without displacing Helm OCI as the primary install path.

Gate Result Notes
preflight / test / kind-smoke / Analyze (Go) trusted per lane report (required checks green)
actionlint (release.yaml) trusted; only pre-existing SC2129 note at ci.yaml:193 repo-wide
SonarCloud quality gate (PR 284) re-queried live: projectStatus.status = OK; new_security_rating = 1. S7636 fix in 1e635e9 verified — no ${{ secrets.* }} remains inside any run: block
shellcheck (new scripts) only SC2016 info on an intentional single-quoted grep pattern
Action pin verification oras-project/setup-oras@1d808f7d… is a real commit == tags v2/v2.0/v2.0.1, and its releases.json covers oras 1.3.2. All uses: are full 40-hex SHAs; no pull_request_target
OLM bundle generation (reproduced out-of-tree) valid YAML; kollect.v<version>; 11 owned CRDs == 11 bundle CRD files; alm-examples fields all exist in the real CRD schemas; icon base64 round-trips; no leftover __PLACEHOLDER__
CSV RBAC vs config/rbac/role.yaml ✅ (today) byte-for-byte identical — but ungated, see D5
operator-sdk bundle validate ⚠️ not available locally and not run anywhere in CI — see D11
e2e-multitenant ⚠️ known-flaky on main; not attributed to this PR
Multi-part stub/TODO rule diff is 1163 insertions / 0 deletions; no TODO/FIXME/stub marker removed or added
ID Sev Area Finding Evidence Blocks?
D1 P1 Release robustness Artifact Hub oras steps run in the critical release path with no continue-on-error, before Publish GitHub Release. An oras-download / login / push failure aborts the job after images + chart are pushed and cosign-signed, leaving a tag with GHCR artifacts but no GitHub Release and no assets (install.yaml, SBOMs, checksums, provenance, sigstore bundles). Contradicts ADR-0708's own consequence: "Soft-fail hub jobs preserve tag-release success". .github/workflows/release.yaml:365-386 vs :416 Yes
D2 P1 Correctness / docs artifacthub.io/images hardcodes ghcr.io/platformrelay/kollect:v0.17.0 with no sync gate and no runbook step. docs/RELEASE.md §3 tells the maintainer to bump only version/appVersion and was not updated; the new test only greps the repo prefix, so it passes forever. From v0.18.0 Artifact Hub advertises — and security-scans — the wrong image. An explicit artifacthub.io/images list overrides AH's automatic extraction, so this is worse than omitting it. charts/kollect/Chart.yaml:84-86; hack/test/dist_artifacthub_chart_test.sh:43-45; docs/RELEASE.md:169-178 Yes
D3 P2 Security operatorhub-pr carries secrets.OPERATORHUB_PAT (cross-repo write: fork push + PRs to two third-party repos) but declares no environment:. The release job documents the model at :92-95 — the protected release environment is what stops write creds being obtained by stripping the eligibility job. gh secret list --env release is empty, so the PAT will land repo-level and be readable by any job on any branch with no approval gate. Not yet live (secret unset today) → P2. .github/workflows/release.yaml:434-441, :457 No
D4 P2 Security operatorhub-pr checks out ref: ${{ needs.release.outputs.tag }} — a mutable tag — and re-verifies nothing, while the release job explicitly refuses to (ref: needs.eligibility.outputs.sha + the TAG_SHA != EXPECT_SHA guard). The code executed with the PAT in env and the bundle submitted upstream come from whatever the tag points at. Mitigated by the protect-release-tags ruleset (create/update/delete restricted, admin bypass only) → P2. Fix: needs: [eligibility, release] + ref: ${{ needs.eligibility.outputs.sha }}. .github/workflows/release.yaml:438, :445 No
D5 P2 Drift gate CSV clusterPermissions is a hand copy of the controller-gen-generated config/rbac/role.yaml (identical today — verified). task verify gates role.yaml, but nothing compares the CSV copy. The next +kubebuilder:rbac change silently ships an under-privileged bundle that installs fine then 403s at runtime. ADR-0708 itself flags "Hand CSV drift risk". config/olm/template/manifests/kollect.clusterserviceversion.yaml:137-237 No
D6 P2 Test Inverted gate: the meta-test requires the placeholder repositoryID 00000000-…, so CI goes red the moment the real Artifact Hub ID is filled in. The test blocks its own remediation. Assert a well-formed UUID instead. hack/test/dist_artifacthub_release_test.sh:25-27 No
D7 P2 Correctness maintainers@platformrelay.github.io is fabricated (new in this PR; absent from main). A github.io Pages host cannot receive mail. Artifact Hub matches owners[].email against a registered AH user for the ownership claim, so Verified Publisher can never succeed as written; community-operators also expects a reachable CSV maintainer. The ADR documents the placeholder repositoryID but not this. config/olm/template/manifests/kollect.clusterserviceversion.yaml:109; artifacthub-repo.yml:3-4 No
D8 P2 Consistency config/olm/ci.yaml is dead — nothing reads it; the script writes its own heredoc operators/kollect/ci.yaml that additionally sets reviewers: [platformrelay]. Two sources of truth. Also reviewers upstream is a list of GitHub usernames whose approvals CI recognises; platformrelay is an org, so authorization likely won't resolve. config/olm/ci.yaml; hack/operatorhub-pr.sh:103-107 No
D9 P2 Correctness installModes advertises OwnNamespace/SingleNamespace: true, but the CSV deployment never consumes olm.targetNamespaces (no WATCH_NAMESPACE downward API, no scoping arg) and grants clusterPermissions. A single-namespace install silently collects cluster-wide — beyond the scope the user selected. …clusterserviceversion.yaml:124-132 vs :272-338 No
D10 P3 Fragility __ICON_BASE64__ is substituted with a /-delimited sed while the digest on the same line correctly uses |. base64 legitimately contains /; today's logo happens to encode with zero slashes (verified) so it works, but any logo change breaks generation — and in the release path that failure is swallowed by continue-on-error. Makefile:249-250 No
D11 P3 Test depth The bundle gate is grep-only: no YAML/CSV schema parse, no operator-sdk bundle validate --select-optional suite=operatorframework (what upstream CI runs). Assertions are real, not tautological — just shallow for a hand-templated bundle. hack/test/dist_olm_bundle_test.sh No
D12 P3 Portability for d in "${CLEANUP_DIRS[@]}" under set -u with an empty array is unbound on bash 3.2 (macOS), so the EXIT trap aborts a local DRY_RUN=1 run. CI (bash 5) unaffected. hack/operatorhub-pr.sh:31-33 No

Highest-risk area — outbound third-party PRs (a508caf): what holds up

  • Trigger surface is tight. if: … && github.event_name == 'push' means a workflow_dispatch rebuild (the documented "rebuild assets for testing" path, which can also mark drafts/pre-releases) can not open upstream PRs. needs: release chains back through eligibility, and protect-release-tags restricts tag creation to admins.
  • Idempotent. Branch kollect-v<version> is force-pushed and an existing open PR is updated rather than duplicated (gh pr list --headgh pr edit), so a workflow re-run or repeated release does not spam upstream.
  • No untrusted-input expansion in the PAT-bearing path. VERSION traces back to the regex-validated tag in the eligibility job; the PR title/body contain no attacker-influenceable content; nothing uses pull_request_target; every uses: is SHA-pinned (verified against the upstream repos).
  • Residual gaps: D3 (no environment gate on the PAT) and D4 (mutable-tag checkout of the very code that runs with the PAT).
  • Failure mode is fully silent. continue-on-error: true plus the if [[ -z "$GH_TOKEN" ]] … exit 0 skip means a missing fork, an expired PAT, an upstream rename, or a bundle error all render as a green job. That is the intended "soft-fail", but there is no notification path — consider at minimum a ::warning:: annotation and a step summary line so a failed submission is visible without reading logs.

Functional correctness: ADR-0708's contract is met item by item — (1) Helm OCI unchanged as primary; (2) Chart annotations complete, artifacthub-repo.yml pushed via oras to the chart OCI repo after helm push + cosign, DR-FIND-07 guard retained and ordered correctly; (3) make generate-olm-bundle produces package kollect / channel stable, digest-pinned to the image digest (not the chart digest) in both the deployment and relatedImages; (4) CSV RBAC taken from the real config/rbac/role.yaml; (5) script + soft-fail job with v4.19 OpenShift annotation and platformrelay fork default; (6) skipped items honoured. --validating-webhooks-enabled=false is a real flag (cmd/startup_flags.go:63), not hallucinated. Docs are honest — no fabricated badges, and the guard test enforces that. Failing/at-risk criteria: the AH metadata push is not soft-fail (D1, contradicting the ADR), and the AH image annotation is stale-by-construction after the next bump (D2).

Coverage delta: n/a — no Go production code changed. Shell/YAML meta-tests: 7 new hack/test/dist_*_test.sh scripts, all wired into the CI lint job before task lint (wiring itself locked by dist_ci_wiring_test.sh), two also into hack/docs/verify.sh. Assertions are substantive, not tautological — with the exceptions noted in D6 (inverted) and D11/D2 (too shallow to catch the invariants that matter).

Summary / next steps — required before merge:

  1. D1 — make the Artifact Hub oras steps soft-fail and/or move them after Publish GitHub Release, so hub metadata can never abort a release mid-publish.
  2. D2 — gate artifacthub.io/images against appVersion in dist_artifacthub_chart_test.sh and add it to docs/RELEASE.md §3.

Strongly recommended in the same pass (cheap, and they protect the credential and the bundle): D3 (environment-scope the PAT), D4 (check out the eligibility SHA), D5 (CSV↔role.yaml drift gate), D6 (un-invert the repositoryID assertion). D7–D12 are notes.

Independent review of the diff only; I did not author any of this code and I am not authorising a merge.

@konih

konih commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

REVIEW — Kollect/dist/hub-distribution @ 264a01d5 — APPROVE — 2026-08-07

Verdict: APPROVE (with 2× P2 follow-ups and 4× P3 notes — none blocking)

Fresh independent re-gate of the 8 fix commits 093c92fe3..264a01d5a. I did not write this code and did not perform the earlier review; every claim below is mutation-verified or checked against the live repo API.

Gate matrix

Gate Result Notes
PR check rollup @ 264a01d5 36/36 SUCCESS (preflight, test, kind-smoke, Analyze (Go), lint, verify, helm, vulncheck, CodeQL, gitleaks)
e2e-multitenant SUCCESS at this head — the prior advisory failure no longer applies
SonarCloud quality gate OK, all 5 conditions pass, 0.0% new duplication, 100% hotspots reviewed
mergeStateStatus CLEAN / MERGEABLE
hack/test/dist_*_test.sh (7) all pass in the worktree; wired into CI lint before task lint, with yq installed earlier in the same job (fail-closed if absent)
Go gate matrix n/a no Go production code in the diff
Worktree hygiene git status --porcelain = 0; generated dist/olm-bundle/ is gitignored (.gitignore:42)

The six prior findings

ID Status Evidence
D1 (P1) ✅ fixed The three hub steps are now the last steps of release (.github/workflows/release.yaml:415/422/446), all after Publish GitHub Release (:391), all continue-on-error: true — reporter included, so it cannot reintroduce the abort. Reporters read steps.<id>.outcome, correctly, since continue-on-error pins .conclusion to success. Push gates on steps.setup-oras.outcome == 'success'; that replaces the implicit success() guard but stays safe because setup-oras is skipped on any earlier core failure. Nothing after publish can flip the job result, so needs.release.result == 'success' and operatorhub-pr are unaffected.
D2 (P1) ✅ fixed dist_artifacthub_chart_test.sh:46-77 yq-parses appVersion and asserts the annotation tag equals v<appVersion>, with non-empty guards on both sides and a found_controller_image guard. docs/RELEASE.md:177-192 adds the bump to step 3 with the "explicit list overrides auto-extraction" rationale.
D3 (P2) ✅ fixed environment: release at :484, gated by dist_operatorhub_pr_test.sh:56-58. Side effect is real — see P2-1.
D4 (P2) ✅ fixed needs: [eligibility, release] (:476) + ref: ${{ needs.eligibility.outputs.sha }} (:492), matching the release job's own SHA binding.
D5 (P2) ✅ fixed dist_olm_bundle_test.sh:48-79 — order- and key-order-insensitive normalisation of both sides, diffed, with explicit non-empty guards on both so a mistyped path fails loudly. Reads the generated bundle CSV, so it also covers the generator.
D6 (P2) ✅ fixed dist_artifacthub_release_test.sh:20-33 asserts a well-formed UUID (placeholder and real ID both pass — the gate no longer blocks its own remediation).

ADR-0708 integrity — the highest-risk change in the diff

Legitimate reconciliation, not goalpost-moving. git diff 1e635e9ce..264a01d5a -- docs/adr/0708-operator-distribution-hubs.md is +11/−3, and all three deleted lines are re-emitted text (items 4 and 5 reflowed to gain a clause; old item 6 Skipped: renumbered verbatim to item 7). Every edit adds an obligation:

  • item 4 now requires the CSV↔role.yaml drift gate;
  • item 5 now requires the protected release environment and the eligibility-proven SHA;
  • a new item 6 requires that hub steps run after Publish GitHub Release and that soft-fail be visible via steps.<id>.outcome.

Decisively: the Consequences line D1 was measured against — "Soft-fail hub jobs preserve tag-release success when PAT/forks are absent" (:88) — is untouched, and the workflow now actually satisfies it. The ADR was tightened around the fix, not loosened around the bug. I also grepped for citations of ADR-0708 by item number; the 6→7 renumber breaks nothing (only file-level links exist).

Non-vacuity — 12 mutations, all in temp copies (worktree untouched)

Mutation Expected Result
D2: bump appVersion 0.17.0→0.18.0, annotation untouched red ✅ red, exact message
D2: annotation tag →v0.16.0, appVersion untouched red ✅ red
D5: delete one - watch verb from CSV red ✅ red, rule-level diff
D5: add nodes to a CSV resource list red ✅ red
D5: append a bogus rule to role.yaml only red ✅ red
D5: rename CSV serviceAccountName anti-vacuity guard ✅ "would pass vacuously"
D6: repositoryID: not-a-uuid red ✅ red
D6: plausible real UUID green ✅ green (gate doesn't block remediation)
D1: move Set up oras before the publish red ✅ red, with indices
D1: strip continue-on-error from the reporter red ✅ red
D3: remove environment: red ✅ red
D4: revert ref to needs.release.outputs.tag / drop eligibility from needs red ✅ red (both)

Controls green in every case. No gate in this diff passes vacuously.

Findings

ID Sev Area Finding Evidence Blocks?
P2-1 P2 Docs / ops The environment: release gate's operator-visible cost is documented only in a YAML comment and the ADR, never in the runbook. Verified live: the release env carries a required_reviewers rule, so every release now stops for a second manual approval after the release job already succeeded; timeout-minutes: 15 does not cover the waiting-for-approval state, so the run sits open until a human acts; rejecting it marks the terminal job failed, so a successfully published release shows a red run; and OPERATORHUB_PAT is in neither repo nor release env secrets, so post-approval the step self-skips — today the prompt buys a no-op. A dedicated environment is not the fix: the v*.*.* tag policy alone stops nothing an attacker's tagged workflow copy couldn't match, so the required-reviewers rule is the entire security value and dropping it would be strictly weaker. One paragraph in docs/RELEASE.md §"Cut a release" + "PAT must be a release environment secret". docs/RELEASE.md:203-236 vs .github/workflows/release.yaml:478-484 No
P2-2 P2 Tests CSV spec.install.spec.permissions (namespaced leader-election rules) still an ungated hand copy of config/rbac/leader_election_role.yaml. I diffed by hand: currently in sync, so latent risk not live bug. Honestly flagged as deferred, but the RULE_NORMALIZE machinery landed for D5 applies verbatim — ~6 lines to close. config/olm/.../kollect.clusterserviceversion.yaml:238; hack/test/dist_olm_bundle_test.sh:57 No
P3-1 P3 Tests If repositoryID: is absent entirely, the grep | head | sed pipeline fails inside $( ) and set -euo pipefail aborts before the [[ -n ]] guard can print. Verified: exit 1, zero diagnostic. Fail-closed, but mute. Fix: | head -1 || true. hack/test/dist_artifacthub_release_test.sh:26 No
P3-2 P3 Tests Step-graph gate asserts publish < oras/push/report but never push < report. A reorder would leave the reporter reading an empty outcome and reporting success on an unpublished push. hack/test/dist_artifacthub_release_test.sh:89-94 No
P3-3 P3 CI The release job has no timeout-minutes (only operatorhub-pr does). Pre-existing, but this PR moved two unbounded network steps to the job's tail, so a hung oras push now holds the runner for the 6h default after the Release is published, with operatorhub-pr queued behind it. .github/workflows/release.yaml:90-470 No
P3-4 P3 Tests The …/kollect@* case arm sets found_controller_image=1 then always fails, since EXPECTED_IMAGE is only built in tag form — a digest-pinned entry is rejected with a misleading "appVersion" message. Also, a future kollect-pipeline entry falls through unchecked. hack/test/dist_artifacthub_chart_test.sh:63-70 No

Adversarial checks that did not produce a finding

  • Could the moved steps flip needs.release.result and skip operatorhub-pr? No — all three are continue-on-error, the reporter included, and the reporter is the job's last step.
  • Does if: steps.setup-oras.outcome == 'success' dropping the implicit success() guard let the push run after a failed publish? No — setup-oras has no if:, so it is skipped on any earlier failure and the condition is false.
  • Did D6 delete a marker while implementing only part of it? No — artifacthub-repo.yml:1-9 carries a dated 2026-08-07 — PENDING OPERATOR ACTION marker naming the exact remediation, and ADR-0708:83-84 still records the placeholder-until-registration consequence. The old assertion was an inverted gate that would have gone red the day someone did the right thing.
  • Was anything else silently dropped? No. D7–D12 untouched as instructed; the only additional deferral (namespaced permissions:) is disclosed and filed above as P2-2.

Functional correctness

All six acceptance criteria are implemented and regression-gated by a test that I independently proved fails when the fix is reverted. The release job's own contract is preserved: hub failure can no longer strand a signed tag without a GitHub Release, and it can no longer suppress operatorhub-pr.

Coverage delta: n/a — no Go production code changed; all new logic is shell meta-tests, each mutation-verified above.

Summary / next steps: Clear to merge from my side. The two P2s are follow-ups (a docs/RELEASE.md paragraph on the second approval prompt + PAT placement, and extending the RBAC drift gate to the namespaced permissions: block); the P3s are polish. Note I do not authorise merges and have deliberately not used gh pr review — the maintainer merges.

konih added 17 commits August 8, 2026 12:59
Record the Artifact Hub and OperatorHub distribution decision under theme 07
as Exploring/Proposed until maintainer LGTM and hub registration land.
Add operator, category, capability, CRD, link, and image annotations so
Artifact Hub can classify Kollect once the chart repo is registered.
Publish artifacthub-repo.yml to ghcr.io/<owner>/kollect:artifacthub.io after
helm push, cosign, and the DR-FIND-07 collision guard.
Hand-templated registry+v1 bundle for package kollect/stable with digest-pinned
CSV deployment and mechanical CRD copy from config/crd/bases.
Add dual-upstream operatorhub-pr.sh with DRY_RUN support and a soft-failing
release job gated on OPERATORHUB_PAT for community-operators submissions.
Document conditional Artifact Hub and OperatorHub install wording without
live listing badge URLs until registration and upstream merge complete.
Cover theme 07 README indexing for the hub distribution ADR.
Wire dist_* scripts into ci.yaml before lint, fix DRY_RUN cwd,
add docs verify hooks, and note OLM webhook-disabled install path.
The chart test only grepped the ghcr.io/platformrelay/kollect: prefix, so the
hardcoded artifacthub.io/images tag passed forever regardless of version. An
explicit images list overrides Artifact Hub's automatic image extraction, so a
drifted tag makes the hub advertise and security-scan the previous release's
image. Parse appVersion and the annotation from the same Chart.yaml with yq and
fail loudly on mismatch.
Step 3 told the maintainer to edit only version/appVersion, leaving the
hardcoded Artifact Hub image tag stale by construction from the next release
onward.
The setup-oras and Push Artifact Hub metadata steps sat in the critical path of
the release job with no continue-on-error, ahead of Publish GitHub Release. An
oras download, login or push failure therefore aborted the job after the
controller image, pipeline image and Helm chart were already pushed AND
cosign-signed, leaving a tag with published signed GHCR artifacts but no GitHub
Release and none of its assets (install.yaml, SBOMs, checksums, provenance,
sigstore bundles).

Move both steps after Publish GitHub Release and mark them continue-on-error, as
ADR-0708 already contracts ("soft-fail hub jobs preserve tag-release success").
Soft-fail is not silent: a new reporting step reads steps.<id>.outcome (which
continue-on-error does not mask, unlike .conclusion) and emits a ::warning::
annotation plus a GITHUB_STEP_SUMMARY line. dist_artifacthub_release_test.sh now
gates the ordering, the soft-fail flags and the reporting so this cannot regress.
…oven SHA

The operatorhub-pr job carries secrets.OPERATORHUB_PAT — a cross-repo write
credential for k8s-operatorhub/community-operators and
redhat-openshift-ecosystem/community-operators-prod — but declared no
environment, so the token was reachable without the protection the release job
documents at its own environment declaration.

It also checked out ${{ needs.release.outputs.tag }}, a mutable ref, while the
release job deliberately binds to the immutable SHA proven by eligibility
("never the mutable tag ref alone"). Add needs: [eligibility, release] and check
out needs.eligibility.outputs.sha instead.

The submission step's soft-fail is now reported through steps.<id>.outcome with a
::warning:: annotation and a job-summary line rather than being silently green.
dist_operatorhub_pr_test.sh gates all four properties.
The CSV clusterPermissions block is a hand copy of the controller-gen-generated
ClusterRole and nothing kept the two in sync. The next +kubebuilder:rbac change
would have shipped an under-privileged OLM bundle that 403s at runtime while the
Helm chart kept working. Compare the generated bundle CSV against role.yaml with
an order-insensitive normalisation, with non-empty guards on both sides so a
mistyped yq path cannot make the gate pass vacuously.
The gate required the literal placeholder repositoryID, so CI would have gone
red the moment the operator pasted the real Artifact Hub ID after registration —
the check blocked its own remediation. Assert a well-formed UUID instead, and
re-document the still-pending registration as a dated marker in
artifacthub-repo.yml so the context the old assertion carried is not lost.
The two new outcome-reporting steps ran with set -euo pipefail and no
continue-on-error, so a failure inside the reporter would have failed the release
job after the signed publish -- exactly the failure mode the reordering exists to
prevent -- and would also have flipped needs.release.result away from 'success',
silently skipping operatorhub-pr. Mark both soft-fail and extend the meta-tests
so no post-publish step can regress to hard-fail.
ADR-0708's contract described the OperatorHub job only as PAT-gated and
soft-fail. Record the protected-environment gate, the eligibility-proven SHA
checkout, the RBAC drift gate, and the rule that hub steps run after the release
publish and report failures visibly.
@konih
konih force-pushed the dist/hub-distribution branch from 264a01d to 2b5ec9e Compare August 8, 2026 10:59
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@konih
konih merged commit d2f8292 into main Aug 8, 2026
36 checks passed
@konih
konih deleted the dist/hub-distribution branch August 8, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants