release-train: staging -> main - #526
Open
LukasWodka wants to merge 6 commits into
Open
Conversation
…nswer (cli#504) (#518) * feat(interactive): the guided `?` line carries a label, not just an answer (cli#504) The guided flow built its prompter as `surveyPrompter{bare: true}`, which set survey's Message to "". The prompt line rendered as a lone `?` — and since #505 started pre-filling answers, as `? [~/mydata]`: a question mark, a bracket and a path, with no verb. The bare mode's premise was sound (the CLI already prints `Step 3 of 4 · Where is your data?`, so repeating it on the `?` line would duplicate it) but the conclusion was not — and the codebase already said so. `Confirm` refused to go bare because "a bare `? (y/N)` there would be a label-less destructive prompt"; that objection was never Confirm-specific. Each guided prompt now passes a short noun label: the shortest noun phrase that names the answer, with a trailing colon. `? Path: ~/mydata`, `? Task: tabular_classification`, `? Column types:`. The header still asks the question; the label says what you are typing into. The label-column question keeps its two wordings on both lines — `Label:` for a class, `Target:` for a numeric value — so the branches stay distinguishable on the prompt line too. `bare` is deleted rather than left unused, so no future call site can reach the label-less rendering. Flows with no step header of their own (client create, delete, resources set) are untouched: they still pass the whole question, which is right for them. Tests: the ~110 scripted answers keyed by prompt label are rekeyed across interactive_test.go, copy_catalog_test.go and task_scope_test.go (the issue's file list missed the third; path_examples_test.go turned out to key on nothing). Two assertions were rewritten rather than rekeyed, because rekeying would have made them vacuous: the #181 file-or-folder copy check now reads the PRINTED step (a short label cannot carry that sentence), and the MLM no-label-question check names both `Label:` and `Target:` instead of matching a shared "Which column holds" stem that no longer exists. New guard TestRunInteractive_EveryGuidedPromptCarriesAShortLabel drives the real flow across seven scenarios and asserts a property of whatever it asks — non-empty, ends in ':', carries no '?', within a 16-rune budget — with the confirm asserted to be the opposite (a whole question). Nothing is scripted by label, so there is no list agreeing with itself; zero recorded prompts is a failure, not a pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): bump VERSION to 0.10.9 (cli#504) version-bump-gate failed this PR: v0.10.8 is already released and the diff touches published paths (internal/*). The release train reads VERSION and cuts the tag from it — it never bumps for you, so leaving it stale does not fail here, it fails the next prod hop days later on somebody else (backend#1561). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…520) * fix(sanitize): strip SS3 escapes and floor escape-only names (cli#516) sanitizeClientName handled CSI (ESC '[' … final) only. SS3 (ESC 'O' final) is what the same arrow / Home / End / F-keys emit once the terminal is in DECCKM application-cursor mode — the state vim, less or tmux leave behind on an unclean exit. That residue was worse than the CSI residue fixed in cli#364 / client#362 (2026-07-21, not re-litigated here): CSI cleans to empty and re-prompts, while 'O' and the final byte are printable, so ESC OD ×3 ESC OA ×3 survived as the plausible name "ODODODOAOAOA" and minted the permanent namespace "odododoaoaoa". Nothing downstream can refuse it: is_dns1123_label validates by idempotence against the slug rule, so escape-derived garbage is a perfectly canonical label. Form is exactly what this input preserves. Two changes, both in sanitizeClientName — deliberately NOT in internal/slug, which must stay a faithful mirror of backend/common/utils/slug.py: 1. escSequence now matches CSI and SS3 in one pattern. 2. A post-sanitise floor. If an ESC SURVIVES step 1 the value carries an escape family we do not recognise — which is precisely how SS3 got here — so it must show one alphanumeric that did not come from an escape final byte, probed with a greedier pattern whose output is never returned. Nothing but residue returns "", the same path an omitted --name takes. Scoped to "an ESC survived" so a clean name never reaches it and real content beside an unknown escape is kept; the failure it chooses is the recoverable one. Tests: 10 new cases in the table (SS3 arrows / Home-End / F-keys / mixed with CSI / truncated / a bare O is not an escape; the floor with SS2 standing in for "the next family", including the non-Latin-content case) plus a test pinning the ticket's exact repro and the slug it used to mint. Mutation-proven, three anchors, each applied and each detected: • SS3 dropped from escSequence -> 2 cases red ("na\x1bODme", SS3+CSI mixed) • floor short-circuited to false -> 2 cases red (truncated SS3, unknown family) • hasAlphanumeric made ASCII-only -> 1 case red (non-Latin content) The "SS3 arrows only" case is green under anchor 1 because the floor also covers it; anchor 1 is carried by the mixed-content cases, which the floor cannot mask. The bash and PowerShell peers get the same two changes in tracebloc/client. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): VERSION 0.10.8 -> 0.10.9 (cli#516) version-bump-gate is a required check and it refuses a PR that touches internal/* while VERSION still names an already-released version: v0.10.8 is out, so shipping this fix under it would put different bytes under an existing release. 0.10.9 is untagged and above every released final version, and it is the same target the other two open PRs on develop bump to — identical one-line changes merge without conflict, and all three then ship under the pending 0.10.9. Not a hand-cut release: the release train still reads this file and cuts the tag from it at the prod hop. The gate's own message is explicit that it never bumps for you, and that a stale VERSION fails days later on somebody else's hop (backend#1561) rather than here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(sanitize): bound the floor's probe to two final bytes (cli#516) Bugbot, Medium, on tracebloc/client#736: the floor's probe used an unbounded `[A-Za-z~]+` after an unrecognised ESC, so every ASCII letter following the escape was swallowed into the probe and the value read as residue-only. It is right, and the sharper half of it is the part I had not seen: `\x1bNChello` was refused while `\x1bNC日本` was kept, which makes keep-vs-reject depend on the script the user's name is written in. I had accepted the over-strictness on purpose; I had not noticed it was inconsistent. Bounded to `{1,2}`. Two, not one and not unbounded: one leaves the 'D' of an unrecognised SS3-shaped pair behind and the floor stops firing on the exact family shape this ticket is about, while unbounded eats a whole name. An escape final is one byte, an intro plus a final is two, and every keyboard-input escape family (SS2, SS3, the 7-bit C1 forms) fits in that — so the bound is a statement about escapes rather than a tuning constant. Every case the floor is meant to catch is unaffected: ESC N B / ESC N C, a truncated ESC O, and ESC [ ; ] A all still collapse to empty. Applied to all three copies so the rule stays one rule. Mutation-proven: reverting `{1,2}` to `+` turns the new case red in Go ("\x1bNChello" -> "") and in bats. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…s a way back (cli#515) (#519) * fix(cluster): say which namespace, instead of offering --namespace blindly The §7.3 binding-miss error is RFC-0001's sentence with the remedy deleted: it names --namespace but never says WHICH namespace, and nothing else in the CLI will tell you. On a laptop with a healthy local install that left no supported way back. explain now diagnoses before advising. A binding-applied noParentReleaseError carries the clientset and server URL of the cluster that actually missed, and explain spends one naming-only cluster.FindClientNamespaces — the same read discoverRelease already spends purely to write a better message — then branches on isLocalServerURL: one client + local server URL name it, offer `client create` (a re-run on a cluster that already hosts a client adopts it, so the repoint mints nothing) client(s) on a remote cluster name the namespaces, offer ONLY --namespace; never `client create` there, because the client we found may be a colleague's (§7.5) none, scan clean today's text plus "No tracebloc client is running on this cluster either", which is when the installer is the right advice could not look today's text, byte for byte The last branch is the point of the three-valued clientSurvey: a nil probe or a failed scan is an absence of evidence, and printing it as "nothing is running here" would tell a user with a working client the opposite of the truth. allowScan() is untouched and still false for an applied binding: this changes what the CLI says, never what it targets. TestActiveClientBinding_AllowScan and TestDiscoverRelease_NoScanWhenExplicit pass unmodified, and TestExplain_BindingMiss_NamesTheLocalClientWithoutRetargeting pins both halves at once — the namespace appears in the message and nowhere else. The probe travels on the error rather than through explain's signature so a caller cannot hand it a clientset for a different cluster than the one that missed; six of the seven call sites never held one anyway (resolveClusterTarget builds it internally and returns nil on the error path). Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(doctor,home): a wrong pointer is not proof there is no environment #401 taught the home screen that an EMPTY active-client pointer says nothing about what runs on this machine. The wrong-pointer case was never covered, and it is the worse one: doctor binds the stale pointer, probes only that namespace, never scans, and prints "No secure environment on this machine yet" with the installer command underneath — over a perfectly healthy install. home has the same hole from the other side: its local-env fallback sat behind `if !binding.applied`, so a non-empty pointer skipped the #401 fix entirely. Both now route the miss through the same fallback: home the ErrNoParentRelease branch returns localEnvFallback(ctx) instead of a bare localNoRelease. Every failure inside the fallback degrades to localNoRelease, so this branch's old return value is still its floor. doctor on a ReachNoEnv result that a BINDING (not the user) aimed, re-probe the namespace the kubeconfig itself selects, via localEnvNamespace. The ownership gate is what makes this safe, and it is unchanged: both adopt only when isLocalServerURL says the kubeconfig's server is this machine — a cluster that is this machine by definition, so whatever runs there is this machine's environment. On a remote or shared cluster the honest no-environment answer stands, and a colleague's client is never greeted as yours (§7.5). No scan is spent either: the installer points the kubeconfig context at the client's namespace (client/scripts/lib/install-client-helm.sh runs `kubectl config set-context --current --namespace <ns>`), so reading the context is enough to find a healthy install that the pointer missed. doctor keeps the original results unless the re-probe actually finds an environment, so a genuinely bare machine still gets the installer advice and the --diagnose bundle still describes the namespace the user is configured for. An explicit --namespace is never second-guessed — no binding, no re-probe. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(client): list `create`, and stop calling a stale pointer "this machine" Two things the diagnose-before-advising fix needs in order to reach anyone. `client create` is visible. It was Hidden because a human running it standalone on a cluster with no client mints one the installer never deploys — an orphaned phantom (backend#970). That risk is real and unchanged, but hiding the command was never what prevented it. The two guards that do are untouched, and now have tests naming them: • on a TTY, the review + "Provision this client?" confirm. A re-run on a cluster that already hosts a client never reaches it — adoption happens first — so the repoint stays prompt-free and mints nothing. • off a TTY, a hard refusal without --yes/--credential-file, so a pipe or CI can never mint silently. What hiding did cost is #515: the one command that repoints a machine was unlisted, so the error telling a user to repoint pointed at nothing they could find. Its Short/Long now describe what it does for a user (adopt/repoint) rather than the installer's use of it. `client list` stays hidden. `client list` marks residency, not just selection. It labelled the active pointer "(active — this machine)" without ever checking where that client runs — so in exactly the state this ticket is about, the listing sat there confirming a client provably not on this machine. Selection (the local pointer) and residency (does it run on the cluster the kubeconfig reaches, keyed on the §7.2 cluster anchor) are now two separate facts, and a mismatch names the repoint. An unreadable anchor is a third state, not a "no": with no kubeconfig or an unreachable API server, no row claims to be here and none is denied — the marker degrades to bare "(active)". The installer's #303 pre-flight is unaffected; the markers sit in the row label and the greppable `namespace=<ns>` field is untouched (client_list_contract_test.go still passes). Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(cli): regenerate the copy catalog; `client create` is no longer hidden The golden diff is the whole user-visible change, reviewed line by line: 08-client.golden `create` now appears under Available Commands, with its new Short and the Long that explains adoption. zz-all-strings.golden the five §7.3 branches and the `client list` mismatch hint. Each branch of repointMessage is one format literal rather than a `+`-joined string, because the catalog's AST harvest only sees literal arguments — the message it replaced was invisible there for exactly that reason, and half a sentence in the completeness backstop is worse than none. cli-navigation.md carried two statements this change makes false: it drew `client create` as a hidden node, and its exit-4 remedy line said "run the installer (or --namespace)", which is now only one of three answers. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): bump VERSION to 0.10.9 v0.10.8 is already released and this PR changes published files under `internal/*`, so version-bump-gate (a required check) asks for the bump here rather than leaving it to fail the next prod hop on somebody else (backend#1561). The release train reads VERSION and cuts the tag from it. Patch, matching this repo's dominant pattern for user-facing copy and surface changes — say so on the PR if 0.11.0 is wanted for the `client create` unhide. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(cluster): say why explain replaces the error instead of wrapping it `%w` is the house convention (~325 sites), which makes a bare errors.New here read as an oversight. It is the same deliberate replacement the fmt.Errorf it replaced did: the §7.3 guidance is meant to BE the message, not to trail the raw "no release in namespace X". Wrapping would also make the result re-match errors.As(*noParentReleaseError) and so re-explainable. Recorded in place so a reviewer doesn't have to re-derive it. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(doctor,home,client): finding the environment is only half the story Both Bugbot findings on this PR were right, and both are the same mistake: a two-valued answer where the honest answer has three values. HIGH — "stale pointer still blocks after fallback". Re-probing found the healthy environment and then said nothing about the pointer that missed. So doctor printed "Everything looks good — you're ready to run training" and exited 0 while `data`/`resources`/`seal` all still bind the stale namespace and exit 4; before this PR that state at least exited 3. doctor now names the stale pointer and the repoint, and exits 2 — the code it already uses for every actionable finding. A problem WAS found; it just isn't in the cluster. The home half was worse, and was newly introduced here: local liveness came from the fallback's client while the heartbeat is still looked up by the STALE client's id, so a colleague's machine being online could render this one green. envProbe carries pointerStale, and resolveHomeModel refuses both directions off it — a stale heartbeat can no longer green the screen, nor harden into "backend reports not online" for a client it isn't about. It drops to "running, couldn't confirm", which is exactly true. MEDIUM — "empty cluster ID marked absent". `client list` compared anchors as a boolean, so a client whose OWN anchor is empty — legacy / not-yet-backfilled, which api.ProvisionedClient documents — was reported as "NOT on the cluster your kubeconfig reaches", with the repoint hint, possibly while running on this very machine. Exactly the collapse this PR's cluster-anchor handling was careful to avoid, missed one level down. Residency is now a three-valued residencyOf(): either anchor missing is resUnknown, and unknown claims nothing either way. realProbeEnv moved to home_local_fallback.go: home.go went 13 lines over its file budget, and the probe is now mostly a decision about WHICH fallback to take, so it reads better beside them than beside the renderer. Six mutations, each with its anchor asserted and each reddening an assertion rather than the compiler: collapse the empty client anchor; let doctor green a stale pointer; drop doctor's stale-pointer note; stop marking the fallback's result stale; let a stale pointer render Online; let another client's not-online harden into a verdict. TestDoctor_HealthyPointer_StillGreen is the control — without it, "never says Everything looks good" would pass vacuously. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(doctor): put the stale-pointer finding IN the readiness line Rendering the previous commit showed the fix was half done: the closing verdict was honest, but the line above it still read ✔ Connected to tracebloc ✔ Ready to run training ⚠ Your active client points at namespace "stale-ns" … — a green tick and, directly beneath it, a warning saying the opposite. That is the same unearned success the finding was about, moved up the screen. `Ready to run training` is false whenever the pointer is stale, however green the cluster checks are, because every data command binds the pointer. So the readiness healthLine is replaced rather than accompanied, and it carries the remedy, so the finding and the fix read as one thing: ✔ Connected to tracebloc ✖ Not ready — your active client points at namespace "stale-ns", which isn't on this cluster, so data commands will keep failing until you repoint. Point this machine at the environment above: tracebloc client create (this cluster already runs a client, so it adopts it — no new credential) Phrased "Not ready — …" to match the three readiness failures already in the catalog. `--diagnose` records the replaced line, which is what triage needs. Exit stays 2 via the pointerStale branch, which skips the "email support" nudge a doctorVerdict fail would add — we just gave a precise one-command fix. Three more mutations: disable the replacement (green tick returns) → red; drop the remedy → red; stop naming the stale namespace → red. The already-added TestDoctor_HealthyPointer_StillGreen now also asserts the green tick IS present when nothing is stale, so "no green tick" can't pass by the line disappearing entirely. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(client,home): don't name a target we haven't confirmed exists Bugbot round 2, two Mediums, and the same class a third time: a sentence asserting something the code never established. `client list` set the mismatch hint from the ACTIVE row alone, so with the pointer elsewhere and nothing confirmed on this cluster it still said "point this machine at the client that IS there: … client create". There may be no client there — and on a cluster with none, `client create` falls through to the MINT path and produces the orphaned phantom backend#970 exists to prevent. So this PR's own advice could manufacture the bug the command was hidden for. The repoint is now offered only when some row is resHere — that is what earns the phrase "the client that IS there". Otherwise the mismatch is still reported, without a target: "no client here is confirmed. Check your kubeconfig context, then run: … doctor". Deliberately covers BOTH remaining cases, because they are equally unnameable — no client here at all, and clients that might be here but carry no anchor to prove it (resUnknown). The home screen had the label version of the same thing: with a stale pointer, `env.name` was still overridden by the remembered handle, so the client the pointer names was printed as the environment running here — a client that is by construction NOT what the fallback found, and a name contradicting doctor's for the identical state. The override is now skipped when the pointer is stale, so the screen falls through to the probe's own name for the release that is actually running. Three mutations: let the repoint hint fire without anyHere → red; source anyHere from the active row instead of residency → red; restore the unconditional remembered-name override → red. Both fixes carry a control assertion in the same test (the repoint IS offered when a row is here; the remembered name IS still preferred when the pointer is fresh), so neither can pass by the behaviour disappearing altogether. N10's first attempt left `anyHere` unused and reddened the compiler; rewritten to keep it used and re-run. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(doctor): adopt the re-probe only on a CONFIRMED reachable cluster Bugbot round 3, High, and correct. The re-probe adopted on `reachStateOf(retry) != doctor.ReachNoEnv`, and ReachState has four members: ReachUnreachable and ReachError also satisfy that. Both mean "we could not tell". So a stale pointer plus RBAC on the context namespace, or a transient read failure, would have: • named an unconfirmed namespace as `Secure environment "…"`, • set pointerStale and printed "this cluster already runs a client, so it adopts it — no new credential", • and sent the user to `client create` on a cluster that may host nothing, where it does not adopt but MINTS — the backend#970 phantom. Which is the exact absence-as-presence collapse surveyCluster's `looked` and residencyOf's resUnknown exist to prevent, made twice more in the same PR. Adoption now requires a positive confirmation, via reachConfirmedOK(). It is deliberately NOT `reachStateOf(results) == ReachOK`: reachStateOf defaults to ReachOK when the check is ABSENT, which is the right lenient default on the main path and precisely the wrong one here, where the whole question is whether an unproven namespace may be believed. Absent, unreachable and errored all answer "could not tell", and none may authorize naming an environment or advising a repoint. The test derives its input domain from doctor.ReachState's declared surface — every non-OK member, plus the absent case — rather than picking the states that came to mind: mutation coverage cannot see a vocabulary gap, so a future member has to be added to the enum's own list to escape it. Three mutations: restore `!= ReachNoEnv` → three subtests red; make an absent check count as confirmed → red; let ReachError confirm → red. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(bugbot): make the recurring finding on this PR a rule Org standard: "A finding that recurs across PRs becomes a rule: add it to .cursor/BUGBOT.md". This one recurred three times inside a SINGLE PR — a failed scan read as "nothing is here", an empty legacy cluster_id read as "runs elsewhere", and `!= ReachNoEnv` read as "an environment is here" — each found by Bugbot only after the previous was fixed. Three instances of one root cause is past the threshold. BUGBOT.md already had the neighbouring rule, but scoped to the value a function RETURNS ("prefer a three-valued return"). Every instance here got the return type right and then collapsed it at the `if` that consumed it, so the existing bullet didn't catch any of them. The new bullet is about the branch, and names the two concrete shapes rather than restating the principle: • a negated comparison against ONE member of a multi-valued enum, which silently absorbs every member added later — with the corollary that the test's input domain must come from the enum's declared surface, since mutation coverage cannot see a vocabulary gap; • a lenient "not found" default reused where the question is "may I believe this?" — reachStateOf returning ReachOK for an ABSENT check is right for a verdict roll-up and wrong for authorising a claim, which is why reachConfirmedOK exists beside it. It closes with the customer-visible cost, per this file's own Tone section: each instance ended in advice to run `client create` on a cluster nothing was confirmed on, where it mints instead of adopting — the guidance manufacturing the orphaned phantom backend#970 is about. Refs cli#515 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…e window (#517) (#521) * fix(auth): a transient poll failure retries; the expiry copy names the window cli#517. The device-poll loop's `default:` branch was terminal, so anything that was not one of the four RFC 8628 sentinels ended the sign-in — a DNS blip, a backend restart, a proxy 502. Inside a ten-minute human-paced window that is a long exposure, and under the installer it threw away a run that had already built a cluster. The default is inverted: unknown failures retry, and every terminal state is now enumerated in classifyPollError — the four sentinels, a 426 version floor, a cancelled context, and any *APIError that is not 5xx / 408 / 429. So a server's refusal still stops on the first poll; only failures that never reached a verdict are ridden out. Retries are bounded by maxPollFailures consecutive failures (reset by any answer), so an unreachable backend reports itself instead of burning the code's window and then blaming the user. Also from #517: • the expiry message names the window ("sign-in codes are valid for 10 minutes"), derived from the server's expires_in rather than hardcoded — without it a ten-minute timeout reads as an instant failure; • "Run `tracebloc login` to start a new one" is suppressed when TRACEBLOC_INSTALLER is set. That advice is right for a hand-typed login and wrong under the installer, which prints its own next step; the two used to contradict each other on screen. • a Ctrl-C landing mid-request now exits quietly, like one landing between polls, instead of reporting the operator's interrupt as a sign-in failure. Every message stays a literal argument of errors.New / fmt.Errorf so the copy catalog's AST harvest can still see it; TestCopyCatalogSeesTheSignInStrings pins that, because composing copy inside a helper drops it from the catalog silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): VERSION 0.10.8 -> 0.10.9 version-bump-gate: v0.10.8 is already released and this PR changes a published path (internal/*), so the train would otherwise cut the next tag from a stale file. 0.10.9 is the same pending version cli#518, #519 and #520 bump to — they all ship under it together, and the identical change merges without conflict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…104 invisible) (#522) * fix(test): the copy catalog skipped every message written as a join harvestMessages type-asserted arguments straight to *ast.BasicLit, so a message split across source lines — fmt.Errorf("unknown backend environment %q — valid values are … "+ "set CLIENT_ENV or pass --env", env) — is an *ast.BinaryExpr and was skipped ENTIRELY. Not the second half: the whole message. This file's own header calls the golden "the completeness backstop", and it passed forever while a whole syntactic class of copy was invisible to it. 104 previously-unseen messages, 0 removed. They are not marginal — they are the long validation errors that tell a user how to fix their data: the BOM in an Excel "CSV UTF-8" export, non-UTF-8 CSVs, masks that don't match the image resolution, labels.csv rows referencing absent images, symlinks in the dataset tree. The copy most worth guarding against drift was the copy the guard could not see. literalString folds ADD chains of literals (and parenthesised ones), refusing any join with a non-literal operand. That refusal is the load-bearing half: emitting the literal fragments of a part-computed message would put a sentence in the catalog that no user ever sees, and mark it inventoried while the real text drifts. Absent is honest; half is not. Proven in BOTH directions on the same mutation — breaking the reported message in auth.go: with the fix TestCopyCatalog FAILS without the fix TestCopyCatalog passes <- the guard could not see it TestLiteralString pins the fold with inputs written down independently of the matcher, so a typo in one cannot plant itself in the other; reverting the fold reddens 5 of its cases. TestHarvestMessages_SeesConcatenatedCopy pins the reported defect itself. Found while doing cli#517 (#521), where new copy composed inside a helper vanished from the catalog the same way; that PR worked around it by keeping every sentence a direct literal argument. This is the underlying scanner gap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(version): 0.10.8 -> 0.10.9 version-bump-gate fails any PR touching a published path while the current VERSION names a released tag, and its publish glob is `internal/*` — which matches internal/cli/copy_catalog_test.go even though a _test.go file ships nothing. 0.10.9 is being cut regardless (cli#518, #519, #520 and #521 all bump to it), so this change genuinely rides under that version; the identical one-line edit merges without conflict. Preferred over the skip-version-gate override: the label is for a false positive nobody should have to reason about later, and the honest statement here is that this is part of 0.10.9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
release-train: develop -> staging
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ 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 f9f1328. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated promotion by the release train (RFC-0008 D14). Head is the train-managed
release-train/to-mainbranch (a mirror ofstaging), so it never collides with a human PR. Merged only when the fr-gate is green.Note
Medium Risk
Changes affect authentication polling, cluster error messaging, doctor exit codes, and visible
client create—security-adjacent and installer-critical paths—but behavior is heavily gated (three-valued residency, confirmed reach only, mint guards unchanged) and covered by extensive new tests.Overview
Release 0.10.9 promotes a batch of CLI fixes and UX changes from staging, centered on wrong or stale active-client pointers (#515), device-login polling (#517), and guided ingest prompts (#504).
Stale / wrong active client (#515) makes
client createuser-visible again as the supported way to adopt a client already on the cluster (repoint without minting).client listseparates selection from residency via a three-valuedcluster_idcomparison and only suggestsclient createwhen a client is provably on this cluster. Exit-4 / §7.3 paths survey the reached cluster before advising: local single-client clusters getclient create; remote/shared clusters get--namespaceonly. Doctor and the home screen re-probe the kubeconfig namespace on local clusters when the bound pointer misses, usereachConfirmedOK(not!= ReachNoEnv) before trusting a re-probe, and treat stale pointers as not ready (exit 2) instead of “everything looks good.”Login (#517) retries transient
PollTokenfailures (DNS, 5xx, etc.) with a consecutive-failure cap, names the sign-in code TTL in expiry copy, and suppressestracebloc loginfollow-up advice whenTRACEBLOC_INSTALLERis set.Guided
data ingest(#504) drops bare?prompts: survey labels are short nouns (Path:,Task:,Label:/Target:) with questions in step headers; tests enforce the label contract.sanitizeClientNamealso strips SS3 cursor-mode escapes (#516) and rejects escape-only residue before slugging namespaces. Copy-catalog harvesting folds string-literal concatenations so splitfmt.Errorfmessages stay inventoried.Docs (
STYLE.md,cli-navigation.md,BUGBOT.md) and version bump align with the above.Reviewed by Cursor Bugbot for commit f9f1328. Bugbot is set up for automated code reviews on this repo. Configure here.