Skip to content

release-train: develop -> staging - #647

Merged
tracebloc-release-train[bot] merged 10 commits into
stagingfrom
release-train/to-staging
Aug 10, 2026
Merged

release-train: develop -> staging#647
tracebloc-release-train[bot] merged 10 commits into
stagingfrom
release-train/to-staging

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed release-train/to-staging branch (a mirror of develop), so it never collides with a human PR. Merged only when the fr-gate is green.


Note

Medium Risk
Wide staging promotion touching CronJob behavior, pod resources, and optional DB credential provisioning; defaults stay off for new flags but chart upgrades will change jobs-manager requests and refresh logic fleet-wide on upgrade.

Overview
Automated develop→staging promotion (chart 1.9.22 → 1.9.28) that rolls up several operational and security fixes rather than a single feature.

Client Helm chart: The image-refresh CronJob script now builds an explicit in-cluster kubeconfig (fixes fleet-wide localhost:8080 kubectl failures), adds flap detection after consecutive failed rollouts (maxRefreshAttempts), and tightens fail-closed annotation reads plus request timeouts on API calls. Auto-upgrade Jobs get activeDeadlineSeconds derived from the Helm timeout via a new tracebloc.durationSeconds helper so hung helm repo fetches cannot block future ticks. jobs-manager default memory/cpu requests are raised and requests.memory is pinned to limits to reduce OOM kills during mass restarts; requests-proxy gains TCP probes on :8888. An opt-in serviceDbAccounts flag (default off) provisions tb_meta / tb_ingest secrets and env for jobs-manager (RFC-0003 S1, no consumers until S2).

CI: Code-quality workflow enables hard-fail action pin enforcement.

Installers / images: Default readiness and API wait budgets increase (300s → 600s / 180s tunable); macOS setup uses bounded Homebrew/Colima/Docker installs. k3s-cuda Dockerfile fixes COPY --exclude ordering and build.sh verifies the rootfs before push.

Tests add regression locks for kubeconfig preamble, service DB secrets, and updated resource defaults.

Reviewed by Cursor Bugbot for commit fcadee6. Bugbot is set up for automated code reviews on this repo. Configure here.

saadqbaland others added 10 commits August 6, 2026 19:26
#637)
* fix(chart): image-refresh builds explicit in-cluster kubeconfig (#634)
kubectl in the alpine/k8s image does not reliably auto-detect in-cluster
config in the image-refresh Job pod: with no kubeconfig on disk it falls
back to http://localhost:8080 and every API call fails, even though the
SA token and KUBERNETES_SERVICE_* env are present. This made the refresh
tick fail on every run fleet-wide (dev/stg/prod), surfacing only as the
generic "cannot read deployment (NotFound, RBAC denial, or API error)"
because the read gate swallowed kubectl's stderr.
- Build an explicit in-cluster kubeconfig from the mounted ServiceAccount
(server from KUBERNETES_SERVICE_*, ca + token from the projected SA
volume) before any API call, so bare `kubectl` targets the real API
server. Verified in a dev pod under the real pod constraints
(readOnlyRootFilesystem, HOME=/home/kubectl, emptyDir mounts).
- Surface the real API error on the fail path instead of >/dev/null 2>&1,
so the next incident is one log line, not a live repro session.
- Regression guards lock the kubeconfig preamble in place.
- Bump chart 1.9.15 -> 1.9.16 (script is chart content; chart-version-guard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(chart): address Bugbot — tokenFile off argv + IPv6-safe API host (#634)
Two Bugbot findings on the in-cluster kubeconfig preamble:
- SA bearer token was on the kubectl argv via `--token="$(cat ...)"`
(readable through procfs). Use `kubectl config set users.*.tokenFile`
— the kubeconfig stores only the PATH, no token value on argv or in the
file, and kubectl re-reads the rotating projected token per call.
- The server URL concatenated host:port without IPv6 brackets, an invalid
URL when KUBERNETES_SERVICE_HOST is IPv6 (client-go uses net.JoinHostPort).
Bracket the host for dual-stack / IPv6 clusters.
Regression guards updated: assert bracketed api_host + tokenFile, and a
notMatchRegex forbidding a regression back to `set-credentials --token`.
Verified in a dev pod (readOnlyRootFilesystem, real SA): tokenFile path set,
no token value embedded, bare kubectl rollout status + get both rc=0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#555): add activeDeadlineSeconds to the auto-upgrade Job
autoUpgrade.timeout ("10m") bounds only `helm upgrade --wait`, not the
earlier `helm repo add`/`helm repo update` HTTPS fetches. On a TCP black-hole
(a corporate proxy that drops packets with no RST) those fetches can hang
with no hard timeout; with concurrencyPolicy: Forbid the hung Job then makes
every future scheduled tick "still active" and skipped, wedging auto-upgrade
until a human kills the pod.
Add activeDeadlineSeconds (new autoUpgrade.activeDeadlineSeconds knob,
default 900s) to the Job spec as a hard wall-clock backstop, mirroring
storage-assertions-check.yaml. Kept comfortably above the 600s helm timeout
plus repo-update headroom so a slow-but-healthy upgrade is never killed
mid-flight. Also declared in values.schema.json alongside the sibling knobs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#555): bump Chart.yaml version and nil-guard activeDeadlineSeconds
Bump chart version 1.9.21 -> 1.9.22 so the chart-content change can be
published (CI gate), and keep appVersion in lockstep with version
("1.9.22") per the repo convention — the previous divergence was the
Bugbot finding.
Default autoUpgrade.activeDeadlineSeconds to 900 in the CronJob template
so a --reuse-values upgrade that omits the new key still renders a valid
integer instead of an empty/invalid value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#555): never let the Job deadline undercut a longer helm timeout
Bugbot: the fixed default activeDeadlineSeconds (900s) could kill a
healthy `helm upgrade --wait` when autoUpgrade.timeout was configured
longer (e.g. 30m), firing before helm's own timeout.
Render activeDeadlineSeconds as max(configured floor, parsed timeout +
300s repo-fetch headroom) via a new tracebloc.durationSeconds helper.
The 300s mirrors the baked-in 900-600 default headroom, so the default
case is a no-op (900) and the `| default 900` reuse-values nil-guard is
preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#561): run macOS install steps under bounded spinners
Homebrew install, `brew install docker/colima`, `colima start`, and the
`hdiutil` DMG install ran under spin_cmd, which has no deadline. A wedged
brew (network stall), a hung colima start (stale VZ VM), or hdiutil on a bad
DMG would hang the install indefinitely behind a spinning cursor with no
timeout or kill.
Move all five to spin_cmd_bounded with sensible deadlines (Homebrew 30m to
allow a fresh-Mac Command Line Tools pull; the others 15m each), matching the
create/helm paths that already bound themselves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(installer): regenerate manifest for setup-macos.sh change (#624)
setup-macos.sh changed in this PR; scripts/manifest.sha256 was stale,
failing the Static analysis --check gate. Regenerated via
scripts/gen-manifest.sh; --check now passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ptops (#625)
* fix(#562): soften API-wait and readiness timeouts for slow/proxied laptops
F8 (_wait_for_api): the hard 60s cap (max=30 x sleep 2) false-failed a slow
machine still loading images on its first kubectl even after `k3d --wait`
returned. Replace with a seconds-based, env-tunable budget (TB_API_WAIT_S,
default raised to 180s), and update the error to note a re-run is safe and how
to extend the wait.
F9 (READY_TIMEOUT): raise the default 300s -> 600s so several GB of images
over a slow/proxied link doesn't leave a healthy-but-slow machine reported as
CLIENT_STATE=starting / "not connected". Updated in all three synced
consumers (scripts/spec/facts.env, summary.sh, install-k8s.ps1); check-facts.sh
passes. Still overridable via the READY_TIMEOUT env var.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#562): align check-facts.bats fixtures with READY_TIMEOUT 600 spec (#625 Bugbot CID 3727520250)
The spec (facts.env) raised READY_TIMEOUT 300 -> 600, but the bats fixtures still
seeded the summary.sh / install-k8s.ps1 consumers at 300. That made the green-path
--check test false-fail (a real drift the gate must NOT report) and left the drift
test rewriting 300 -> 600, which no longer exercised drift once the spec was 600.
Seed both consumers at 600 to match the spec, push the ps1 to a wrong value (900)
in the drift test so the gate genuinely reports ReadyTimeout drift, and bump the
--write test spec to 900 so it actually re-stamps both bash and PowerShell budgets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…626)
* fix(#563): back off image-refresh on a flapping-readiness deployment
A deployment whose readiness oscillates (becomes Ready, then crashes after
warmup right around the rollout-status timeout) fails the acting tick's
`rollout status`. Under `set -e` the tick exits before the success annotation
lands, so `recorded` never advances to the new digest and every later settled
tick re-issues the identical `rollout restart` (~every schedule) — churning
ReplicaSets on a genuinely flapping deployment.
Record the ATTEMPT (target-digest signature + a consecutive-failure count) in
a `tracebloc.io/refresh-attempt` annotation BEFORE the restart, so the counter
survives the set -e exit. After imageRefresh.maxRefreshAttempts (default 3)
in-a-row failures for the same target, skip the restart and surface the
flapping deployment instead of re-restarting forever. The counter resets when
the target digest changes or a rollout finally succeeds. New value +
schema entry + env wiring; default installs stay byte-identical apart from the
new env var.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): resume image-refresh once a flapping deployment settles + bound annotate (#626 review)
Address PR #626 review:
- Backoff was a dead end: after MAX_REFRESH_ATTEMPTS failures the tick
exited before restarting forever, so a deployment that later became
healthy/settled never got the reconciling rollout and stayed on the old
image until a newer digest appeared. Now, while in back-off (which is only
reached on a tick that already passed the settled guard), each settled
observation advances a cooldown; after BACKOFF_RESUME_AFTER settled ticks
the attempt counter resets and refresh retries. A cleared flap resumes and
reaches `recorded == latest`; a still-flapping deployment is retried at
most once per cooldown, never every tick.
- Bound the new pre-restart `kubectl annotate` calls with --request-timeout=15s
(repo rule for non-watch kubectl).
- values.yaml comment updated to describe the resume-on-settle behaviour.
- Chart.yaml version 1.9.15 -> 1.9.16 (chart content changed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): read the back-off attempt counter fail-closed (#626 review)
The attempt-counter read used `get_annotation ... || true`, and
get_annotation piped `kubectl | jq` with no pipefail. A failed
kubectl/jq therefore collapsed to an empty value that the back-off
logic read as "no prior attempts" (0) -- failing OPEN past the guard
and re-restarting a possibly-flapping deployment (violates the
no-fail-open-guards rule).
get_annotation now captures kubectl separately and returns non-zero on
either a kubectl or jq error, so a caller can distinguish a genuinely
absent annotation (success + empty output -> 0 attempts) from a read
error (non-zero return). The attempt read takes the SAFE branch on a
read error: skip the restart this tick rather than churn the deployment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): resume grants one restart, not a burst; fix cooldown off-by-one (#626 review)
Two back-off bugs in the image-refresh cooldown logic:
1. Cooldown resume restarted in bursts (CID 3728456918). On resume the
counter was reset to 0, handing a still-flapping deployment a fresh
budget of MAX_REFRESH_ATTEMPTS restarts across the next consecutive
settled ticks -- a ReplicaSet-churn burst that broke the "at most once
per cooldown" invariant. Now resume sets prev_count = MAX-1 so exactly
ONE restart is issued; if it fails, the persisted count is MAX and the
next settled tick re-enters back-off immediately. A healthy deployment
still succeeds on that one attempt and clears the counter.
2. Cooldown off-by-one delayed resume (CID 3728456929). The branch compared
settled_ticks (excluding the current tick) against BACKOFF_RESUME_AFTER,
so the tick logging "Settled 4/4" still incremented and exited, and
resume happened only the following tick (~15m late). Now it compares the
count INCLUDING this settled tick, so 4/4 resumes on that tick, matching
the log and the "after BACKOFF_RESUME_AFTER settled ticks" contract.
Both fixes reuse the existing single-counter encoding; no new state added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): bound get_annotation read with --request-timeout=15s so a wedged API fails closed, not hangs (#626 Bugbot CID 3728757993)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): replace image-refresh back-off with a plain consecutive-failure counter (#626 Bugbot CID 3728806670)
Simplify the over-engineered cooldown/auto-resume/signature machinery down to
the minimal design #563 calls for. The single-integer annotation is no longer
overloaded (signature + count + cooldown offset); it is now a plain count of
consecutive failed refresh attempts that means one thing.
Removed:
- the cooldown counter, BACKOFF_RESUME_AFTER, cooldown_seen/settled-tick
counting, and the resume-to-MAX-1 single-restart logic (auto-resume);
- the signature built from annotate_args and its target-change reset. This
is the fragile "partial resolve resets back-off" path (Bugbot CID
3728806670) -- deleting it eliminates the finding.
Kept (the correct #563 core):
- settled-guard short-circuit for unsettled deployments;
- digest-change detection (refresh only when the resolved digest changed);
- fail-closed annotation reads (skip the tick on a kubectl/jq read error,
never treat an error as "0 attempts");
- --request-timeout=15s on every non-watch kubectl call.
New semantics: increment the counter (persisted before the restart) when a
rollout is issued but does not settle; reset to 0 ONLY on a genuinely
successful settled rollout; after MAX_REFRESH_ATTEMPTS in a row, STOP
restarting, WARN, and annotate tracebloc.io/refresh-flap-detected for a human /
monitoring. No auto-resume -- a human clears the counter (or a rollout that
finally settles resets it).
Net -30 lines. helm lint + template render clean; embedded script passes
sh -n / dash -n; tick-by-tick simulation confirms flap->stop+flag (no burst),
healthy->reset, read-error->skip, new-digest->refresh, unsettled->short-circuit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#563): make the image-refresh success reset bounded + independent of the digest write (#626 Bugbot CID 3729110045)
On a successful settled rollout the consecutive-failure counter
(tracebloc.io/refresh-attempt) was cleared only by being batched into the
final digest-record annotate, which had no --request-timeout. If that
annotate hung or failed under `set -e`, the counter stayed elevated and the
digest went unrecorded, so later ticks re-restarted, burned the budget, and
could permanently trip the flap lockout while the CronJob stayed green.
Clear the counter (and flap marker) in its own --request-timeout=15s annotate
BEFORE the digest record, so the success reset is bounded and no longer
contingent on the digest write succeeding. Also bound the digest-record
annotate and the rollout restart with --request-timeout=15s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ds (#616) (#639)
* fix(gpu): correct COPY --exclude placement in the k3s-CUDA Dockerfile (#616)
The multi-stage rootfs copy failed with "cannot copy to non-directory: .../bin":
--exclude was placed BEFORE the args (`COPY --from=k3s --exclude=/bin / /`), which
the labs frontend didn't apply, so /bin (a real dir in the static-k3s image) was
copied over Ubuntu's merged-/usr /bin symlink and collided. Match the official k3d
recipe: `--exclude=/bin` AFTER the args, then copy /bin explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gpu): correct COPY --exclude (before args, relative path) + verify rootfs (Bugbot) (#639)
A TRAILING `--exclude=/bin` is parsed as the COPY DESTINATION, so the k3s rootfs copied
to /--exclude=/bin instead of / — the build passed but the image was BROKEN (no rootfs
overlay). My earlier "fix" (moving --exclude after the args) hit exactly this. Correct
form: --exclude BEFORE src/dest, with the path RELATIVE to the source (`bin`, not `/bin`
— the absolute path never matched, which is why the very first build collided on /bin).
Also make build.sh self-verifying: build + load, export the image filesystem, and FAIL if
it contains a `--exclude` artifact or is missing /bin/k3s — so a mis-parse can never
publish a broken image again. Only push after the rootfs verify passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gpu): exclude all merged-/usr dirs so the k3s rootfs overlays cleanly (#639)
The before-args `--exclude=bin` fixed /bin but the build then collided on /lib: Ubuntu
22.04 is merged-/usr, so /bin /sbin /lib /lib64 are all symlinks to /usr/*, while the
Alpine k3s image ships them as real dirs. k3s is a static binary (no shared-lib deps), so
exclude all four (keeping Ubuntu's glibc userland for nvidia-ctk) and overlay only k3s's
static /bin into /usr/bin via the kept /bin symlink. build.sh rootfs verify relaxed to
accept usr/bin/k3s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gpu): exclude the full Ubuntu merged-/usr + /var symlink set (#639)
/lib was not the last collision — /var/run (symlink to /run) collided next. Exclude every
Ubuntu-22.04 top-level symlink dir preemptively (bin/sbin/lib/lib32/lib64/libx32 + var/run,
var/lock); excluding a path k3s does not have is a harmless no-op. k3s is static, so keeping
Ubuntu's glibc userland under those paths and overlaying only k3s's /bin is sufficient.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…xy Deployment (#641)
* fix(#1143): add startup/liveness/readiness probes to requests-proxy
The requests-proxy Deployment declared ports/securityContext/resources/env
but no probes, so the pod was marked Ready the instant the container process
started -- before gunicorn had bound :8888. `tb doctor`'s "Service Bus egress
(requests-proxy) ready" check reads ReadyReplicas, so it showed a false green
even when the proxy could not relay.
Add tcpSocket startup/liveness/readiness probes on the gunicorn listen port
(8888), mirroring mysql-deployment.yaml's three-probe structure. tcpSocket
(not httpGet) because the proxy application lives in the separate
client-runtime repo and exposes no chart-visible HTTP health path; a probe
against an unknown path would 404 and wedge the pod, whereas a successful TCP
accept is the honest "gunicorn is serving" signal the doctor check needs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(chart): bump version+appVersion to 1.9.26 (#1143)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1143): make requests-proxy probe comment honest about tcpSocket
tcpSocket:8888 confirms the gunicorn master has bound the listen socket
— a strict improvement over no probes that catches a container that
never starts — but it does not fully close the false-green: the master
binds before the worker finishes importing create_app() and keeps the
socket bound while a worker crash-loops, so a proxy that can't relay can
still read Ready. A real /healthz in client-runtime would be needed to
close it fully. Probes themselves unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…t can't OOM-kill it (#642)
* fix(#1144): pin jobs-manager memory requests==limits so mass restart can't OOM it
On single-node cluster recovery (Docker/k3d restart -> every pod restarts at
once and contends for node CPU + memory) the jobs-manager pod was killed with
exit 137, both the `api` and `pods-monitor` containers, taking ~9m to recover.
During that churn the client stops heartbeating and `tb` shows the alarming
"couldn't confirm it's connected to tracebloc".
Root cause: both containers ran Burstable QoS with requests.memory (512Mi/256Mi)
well below limits (1Gi/512Mi). Under node memory pressure the kernel OOM-killer
prefers the pod using more than its request, so jobs-manager was the victim.
Fix (chart resources): pin requests.memory == limits.memory for both containers
(api 1Gi, pods-monitor 512Mi) so each holds a Guaranteed memory reservation and
is no longer the node's largest OOM victim -- the same requests==limits pattern
mysql-deployment.yaml already documents. Also raise requests.cpu (api
100m->250m, pods-monitor 50m->100m) so a mass restart doesn't throttle their
startup to the observed ~9m crawl. CPU limits and memory limits are unchanged.
Updated both the values.yaml defaults and the template inline fallbacks (used on
helm upgrade --reuse-values from a release predating these keys).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(chart): bump version+appVersion to 1.9.27 (#1144)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1144): reword "Guaranteed QoS" to the accurate oom_score_adj mechanism (#642 review)
The pod is Burstable (cpu request != limit); only memory is pinned. Pinning
requests.memory==limits.memory lowers the container's oom_score_adj, making it
a less-preferred node OOM victim — same pattern mysql uses (mysql also omits
its cpu limit). Reworded the templates, values.yaml, and the test description.
Also fixed the failing jobs_manager_test resource assertions: api
requests.memory 512Mi -> 1Gi, pods-monitor requests.memory 256Mi -> 512Mi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#643)
* sec(#1528): provision tb_meta + tb_ingest service-account Secrets (S1)
Chart side of backend#1528 S1 (RFC-0003 D10 close-out), paired with
tracebloc/client-runtime's minting PR. jobs-manager mints two dedicated,
single-database MySQL identities to replace the root-equivalent edgeuser;
this wires the Secrets and env they read.
- values: serviceDbAccounts (default false) + optional tbMetaPassword /
tbIngestPassword operator pins, mirroring perExperimentDbCreds /
credmgrPassword. Added to values.schema.json.
- secrets.yaml: TB_META_PASSWORD / TB_INGEST_PASSWORD via the same
generate-once, upgrade-stable 3-tier resolution as TB_CREDMGR_PASSWORD
(operator pin -> existing Secret value -> randAlphaNum), alphanumeric-pin
validation, emitted only when serviceDbAccounts is on.
- jobs-manager-deployment: SERVICE_DB_ACCOUNTS=1 + TB_META_USER/TB_INGEST_USER
+ the two secretKeyRef passwords, rendered only when the flag is on.
Gated so a default install is byte-identical (verified: `helm template` with
the flag off renders none of the new keys). helm-unittest coverage added to
secrets_test.yaml and jobs_manager_test.yaml (49 assertions, green).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* sec(#1528): bump chart version 1.9.27 -> 1.9.28 (chart-version-guard)
templates/values/schema changed, so the chart-version guard requires a
version: bump — a Helm repo only publishes a new version, and an unbumped
edit ships dark or overwrites a published tarball. Additive feature =>
patch bump. appVersion unchanged (wiring only, no app image change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1528): bump appVersion to 1.9.28 in lockstep with version (Bugbot)
.cursor/BUGBOT.md flags a Chart.yaml version bump without the matching
appVersion — app.kubernetes.io/version derives from appVersion, so leaving
it at 1.9.27 would tag upgraded resources as the previous release. Every
prior bump moved the two in lockstep; this restores that.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#1492) (#645)
* ci: arm the action-pin gate — findings fail, not just report (backend#1492)
The pinning work is done: 415 mutable action refs across the fleet on
2026-08-04, zero on every develop branch today (#1490, #1491 closed with that
scan). This makes it stay done.
action-pins has been running here since it reached main, but in soft-fail: it
reports a violation and lets the PR merge anyway. That is the state hand-pinning
already proved insufficient. While #1446 was open to pin one action, #1449 added
a SECOND unpinned call site of the SAME action in a non-overlapping hunk of the
same file. Both merged cleanly, git reported no conflict because there was none,
and it was caught by a human reading the diff rather than by any gate. That
incident is why #1492 exists.
Arming imports no backlog. Measured immediately before this change, across all
15 develop branches carrying this caller: ZERO pin violations. So this flips a
check that is already green into one that stays green.
What it enforces, per D10: third-party and actions/* refs pinned to a full
40-character commit SHA with a trailing exact-version comment; tracebloc/* refs
pinned to @main (Q3). A floating tag can be repointed by its owner at any commit,
which is the supply-chain risk the whole class exists to remove.
The .github repo armed this first and for a specific reason: it publishes these
workflows to every other repo, so its own refs are inherited fleet-wide
(.github#178, backend#1603).
* fix(ci): put action-pins in the with: block, not workflow_dispatch inputs
My first commit anchored on the bare key `all-files:`, which matched its FIRST
occurrence in the file - the workflow_dispatch INPUT DEFINITION, not the
code-quality call's `with:` map. So `action-pins: true` landed as an invalid
input declaration (inputs need description/type), the workflow failed to load,
and code-quality did not run at all on this PR. Measured: 10 of 15 callers hit
this; the 5 that did not are the ones whose first `all-files:` happens to be in
`with:`.
Now anchored on the `with:` that follows the code-quality `uses:` line, so the
insertion point is structural rather than a name that appears twice. Validated
by parsing the result: action-pins present in jobs.quality.with, absent from
workflow_dispatch.inputs. Also restores the trailing newline the first commit
stripped.
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fcadee6. Configure here.

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 10, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit c40677f into stagingAug 10, 2026
19 checks passed
Sign up for freeto 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.

5 participants

@LukasWodka@saadqbal@divyasinghds@shujaatTracebloc@aptracebloc