feat: publish Obol CLI version for the frontend footer - #802
feat: publish Obol CLI version for the frontend footer#802HananINouman wants to merge 4 commits into
Conversation
Write obolVersion into obol-stack-config on stack up / tunnel sync and inject OBOL_STACK_VERSION into the frontend pod so the UI can show the running CLI release. Co-authored-by: Cursor <cursoragent@cursor.com>
Collapse SyncStackConfigVersion to a single server-side apply of obolVersion, quote ConfigMap data values, and drop the dry-run-only namespace create that never applied. Co-authored-by: Cursor <cursoragent@cursor.com>
SyncStackConfigVersion applied an obolVersion-only manifest under kubectl's
default server-side-apply field manager — the same manager SyncTunnelConfigMap
uses for {tunnelURL, obolVersion}. Server-side apply deletes fields a manager
previously owned but no longer sends, so every call dropped tunnelURL.
Reproduced against a live k3d cluster:
apply {tunnelURL, obolVersion} -> {"obolVersion":"0.14.0","tunnelURL":"https://x.example.com"}
apply {obolVersion} -> {"obolVersion":"0.15.0"}
Blast radius: SyncStackConfigVersion runs on every `obol stack up`, directly
after the block that skips the cloudflared chart to preserve a healthy quick
tunnel's URL. Quick-tunnel stacks never recover it in that run — the tunnel
stays dormant, so SyncTunnelConfigMap is not called again. serviceoffer-
controller then falls back to defaultBaseURL for the ERC-8004 registration doc
and the OpenAPI `servers` block, and the frontend loses the agent registration
and marketplace URLs.
Applying under a dedicated field manager leaves tunnelURL owned by kubectl and
untouched; a later SyncTunnelConfigMap still overwrites both via
--force-conflicts. Verified on the same cluster.
Claude-Session: https://claude.ai/code/session_015Yed7pSbwnaEUya6ja3j2z
`obol upgrade` re-syncs the same defaults helmfile as `obol stack up` but did not export OBOL_STACK_VERSION, so obol-frontend.yaml.gotmpl rendered the env var as "". The podspec then differed from what stack up produced and the frontend deployment rolled on every upgrade for no reason. Claude-Session: https://claude.ai/code/session_015Yed7pSbwnaEUya6ja3j2z
bussyjd
left a comment
There was a problem hiding this comment.
Approving after end-to-end validation on a live k3d cluster.
The tunnelURL prune is real and the fix works. Both binaries were run against the same live stack, and both runs took the same Active quick tunnel detected — skipping cloudflared chart branch — so this is the actual scenario, not an analogue.
pre-fix (cb6f2f24) |
fixed (7a0f31a9) |
|
|---|---|---|
ConfigMap after stack up |
{"obolVersion":"0.14.0-rc1"} |
{"obolVersion":"0.14.0-rc1","tunnelURL":"https://<quick-tunnel>.trycloudflare.com"} |
kubectl manager owns |
{obolVersion} — shrank |
{obolVersion, tunnelURL} — intact |
The pre-fix run exited 0 with the tunnel still 1/1 ready, and the sell-offer resume at the end of that same run did not restore the field — confirming it is unrecoverable on a quick-tunnel stack without an explicit obol tunnel restart.
The blast radius is worse than the PR body describes. It is not just the ERC-8004 registration doc falling back to a default base URL. With tunnelURL absent, serviceoffer-controller re-rendered the public catalog against the local fallback, and /api/services.json — served over a live public tunnel — began advertising local-only endpoints for every offer:
"endpoint": "http://obol.stack:8080/services/demo-hello"
Every absolute URL in that document was http://obol.stack, unreachable for any remote buyer. It flipped back to tunnel URLs as soon as the field was restored. Worth folding into the PR body so the severity is on record.
Also verified:
OBOL_STACK_VERSIONis rendered into theobol-frontendpodspec by helmfile.- The ConfigMap read path works with the env var removed — no RBAC change needed, as claimed.
- The footer renders the version correctly with obol-stack-front-end#460 deployed.
Unrelated bug found while validating, filed separately as #804: a transient host-port conflict permanently degrades k3d.yaml.
Write
obolVersionintoobol-stack-configon stack up / tunnel sync and injectOBOL_STACK_VERSIONinto the frontend pod so the UI can show the running CLI release.Pairs with ObolNetwork/obol-stack-front-end#460, which renders the value.
Summary
What changed:
internal/tunnel/tunnel.go—SyncTunnelConfigMapnow publishesobolVersionalongsidetunnelURL, and quotes both values.internal/tunnel/tunnel.go— newSyncStackConfigVersionwritesobolVersionalone, for the stack-up path where no tunnel sync runs. It applies under its own server-side-apply field manager (obol-stack-version).internal/stack/stack.go—syncDefaultsexportsOBOL_STACK_VERSIONto helmfile and callsSyncStackConfigVersionafter the infra deploy.internal/update/update.go—obol upgradeexports the same variable, so the rendered podspec matches what stack up produces.internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl— newOBOL_STACK_VERSIONenv on the frontend container.Why it matters:
The frontend footer currently shows only the front-end image tag. Users debugging a stack have no in-UI answer to "which obol CLI built this?". Two delivery paths are wired on purpose: the pod env is the fast path and the only one that works for a local
pnpm devfrontend outside the cluster; the ConfigMap is the authoritative in-cluster path and is the one that survivesobol upgrade.Risk level: medium — touches
obol stack up's ConfigMap publishing, whichserviceoffer-controllerand the frontend both read.Commit under test:
7a0f31a9Base branch:
mainScope
Review Notes
Field manager is load-bearing (fixed in
a6058c91)The first cut of
SyncStackConfigVersionapplied{obolVersion}under kubectl's default SSA field manager — the same managerSyncTunnelConfigMapuses for{tunnelURL, obolVersion}. Server-side apply deletes fields a manager previously owned but no longer sends, so every call silently prunedtunnelURL. Reproduced against a live k3d cluster in a scratch namespace:Blast radius, had it shipped:
SyncStackConfigVersionruns on everyobol stack up, immediately after the block insyncDefaultsthat skips the cloudflared chart specifically to preserve a healthy quick tunnel's URL. Quick-tunnel stacks never recover it in that run, because the tunnel stays dormant andSyncTunnelConfigMapis not called again (stack.goonly reaches it underst.IsPersistent()). Downstream:serviceoffercontroller.registrationBaseURLfalls back todefaultBaseURL→ ERC-8004 registration doc and the OpenAPIserversblock lose the public origin.getTunnelURL()→ agent registration card and marketplace service URLs lose it too.enqueueDiscoveryRefreshwatches this exact ConfigMap, so even persistent-tunnel stacks (where it is restored later in the same run) re-render every offer and registration twice per stack up.Fixed by applying under
--field-manager=obol-stack-version.tunnelURLstays owned by kubectl and untouched; a laterSyncTunnelConfigMapstill overwrites both via--force-conflicts. Locked in byTestStackConfigVersionApplyArgs_UsesDedicatedFieldManager.Reviewer focus
stackConfigVersionApplyArgs— the dedicated field manager is not cosmetic; see above.internal/update/update.go— without the env line,obol upgraderendersOBOL_STACK_VERSION="", the podspec differs from stack up's, and the frontend deployment rolls for nothing.kubectl create namespace --dry-run=client -o yamlcall is safe: it never created anything (dry-run +-o yamlto a discarded stdout), andbase/templates/obol-frontend.yamlpre-creates the namespace.Known gaps
obol-frontend.yaml.gotmpldigest-pinsv0.1.28-rc3; the footer that reads this data ships in obol-stack-front-end#460. That bump is not in this PR.obol-dev.justfilepasses ldflags forgithub.com/obol/obol-stack/internal/version, but the module isgithub.com/ObolNetwork/obol-stack— Go silently ignores the unknown symbol, sojust buildleavesVersion = "dev". Verified locally (obol version→Version: dev)..goreleaser.yamluses the correct path, so released binaries report a real version. Pre-existing; worth a separate one-line fix, noting that theVERSIONfile also still reads0.0.0.pnpm dev. Kept deliberately, documented in the front-end.env.example.Follow-ups
justfileldflags module path (and decide whatVERSIONshould contain for dev builds).Validation
CI checks:
7a0f31a9)Unit tests:
Cluster behaviour check (live k3d, throwaway namespace) — the SSA prune above, and the fix:
Integration tests:
Flow tests:
Release smoke:
Recommended before merge: one
obol stack upon a stack with a live quick tunnel, assertingobol kubectl get cm obol-stack-config -n obol-frontend -o jsonpath='{.data}'still carries bothtunnelURLandobolVersionafterwards.Live Chain Evidence
Not applicable — no on-chain surface in this change.