Conversation
…e dashboard (Maintenance > Upgrade) instead of the CLI command
The what's-new screen previously appeared only on the next bare dashboard launch. Re-invoke the freshly installed binary with a new internal --show-whatsnew mode from upgradeFinalizePhase (after the footer, on success), so Screen 0 opens at the end of the upgrade rendered by the binary that actually carries the notes: the notes registry is compiled into each binary, so on the download path -- where the OLD binary finalizes -- the hook hands off to the installed binary via execPath, which knows the new notes. Gate to interactive, non-auto-yes runs. Skip when the operator signalled an unattended run -- proxsave --upgrade y, or upgrade-beta.sh -y (now propagated to the finalize as `--upgrade y --localfile`) -- so an automated upgrade under an allocated pty (ssh -tt, Ansible, script -c) never stalls on Screen 0 until the 10-minute timeout. Skip under --dry-run so the new non-bare --show-whatsnew path never writes the seen-flag. Tests: AST guard that the hook runs after printUpgradeFooter under the success gate; the interactive/auto-yes/dry-run gate matrix; the --upgrade y --localfile ordering contract the script depends on; the mode dispatch gate.
…ut help line The run-output panel only advertised 'c copy log' (whole log). Clarify that 'c' copies the FULL log and that a shift+drag selects a section for the terminal's own copy (the dashboard holds the mouse via MouseModeCellMotion, so shift is what bypasses capture for native selection). Text-only; no behavior change.
…ion+drag) macOS terminals (iTerm2, Terminal.app) bypass mouse capture with Option, not Shift, so the native-selection hint now reads 'shift/option+drag' to cover both Linux/Windows (Shift) and macOS (Option). Text-only.
…a column The 'UPDATED' outcome joined every disabled key into one comma-separated line, which the styled result box truncated on a long list (17 keys showed as one 'BACKUP_..., BA...' line). Render a header line plus one '- KEY' per line so the full list is readable. SanitizeText preserves newlines, so each key gets its own row. Update the integration assertion to the two-part format and add a unit test pinning the column layout.
Each notification channel logged a failure twice: the notifier self-logged its
terminal failure AND the NotificationAdapter logged its own outcome line for the
same result. Make the adapter the single voice for a channel's terminal outcome
and demote every notifier's terminal-failure self-log to Debug (the failure is
already returned in the result the adapter reads).
The adapter's generic complete-failure branch now carries the reason at WARNING
("<name>: <error>") instead of a terse "failure reported" with the detail hidden
at Debug. logTelegramOutcome is unchanged, so the relay two-line result and the
two Telegram outcome tests are untouched; the webhook inner "failed after N
attempts" line is demoted because the caller loop already logs each failed
endpoint once. Mid-flow lines (fallback attempts, per-attempt retries, PMF
continue-on-warning, auth-rotation recovery) stay as-is: they are not duplicates.
Levels are otherwise unchanged (survivor stays WARNING, not NOTIFY-ERR), so run
recap counts, Prometheus warnings_total, the pre-dispatch notification snapshot,
exit code, and result-derived sensor status are unaffected. The run-log parser
(classifyLogLine/ParseLogCounts) shared with the what's-new pipeline is untouched.
…provisioning The relay now answers 410 SERVER_PARKED when a purged host still presents its old relay token (design 11.2). This is the phase-4 precondition: an Option-A host whose unused account was parked must clear the stale token and re-provision when it returns. - health/config.go: add ErrHCParked sentinel + map HTTP 410 to it on /api/healthcheck/config. - daemon.go buildReporter: clear the on-disk relay secret on ErrHCParked, mirroring the existing ErrHCAuth self-heal (value-guarded against the exact secret used, so a concurrently minted fresh secret is never clobbered). The next run re-provisions; the host is re-admitted as a recreation. - notify/telegram.go: add errRelayParked + map HTTP 410 in sendViaRelay; Send treats it like errRelayAuthRejected (drop the in-memory secret and re-provision this run). Bounded, no loop. - orchestrator/healthcheck_setup_classify.go: PARKED install message. Legacy v0.29 clients use /api/get-chat-id and never receive 410, so they are unaffected. Tests added in config_test, telegram_test, daemon_relay_provision_test, healthcheck_setup_test; affected packages pass.
…hat-id
Blocker: ProvisionRelaySecret used the legacy GET /api/get-chat-id
handshake, which for a chat-less (Telegram-independent) server returns
409 SERVER_NON_ASSOCIATO before ever issuing a token. Option A could
therefore never obtain a relay secret, and SERVER_PARKED recovery could
never re-provision.
- provisionViaRelay(): POST /api/relay/provision with {server_id} and the
shared X-Proxsave-Version header (unauthenticated: the endpoint issues
the token). Handles 201 notify_secret (adopt), 200 already_provisioned
(nothing to adopt, never reads a missing token), 429 (retryable refusal),
and any other status as an error, never embedding the untrusted body.
- ProvisionRelaySecret keeps the same lock / re-check / persist / confirm
bricks; only the HTTP call changed. get-chat-id remains the unchanged
legacy/Telegram/v0.29 path.
Tests rewritten in relay_provision_test.go for the POST contract: method,
exact path, body carries server_id, X-Proxsave-Version present, no
X-Server-Auth, 201 persist+confirm, 200 already_provisioned no-op, 429
retryable no-persist, 201-no-token, short-secret, empty-baseDir, adopt
under lock. Affected client packages pass.
Reviewer's GuideThis release refactors relay secret provisioning to a dedicated POST /api/relay/provision endpoint with robust rate limiting and retry logic, introduces post-upgrade "what's new" display plumbing and CLI flag handling, improves healthcheck handling of parked servers, and rebalances notification-channel logging to centralize terminal outcome reporting while tightening UI and install-flow behaviors. Sequence diagram for relay secret provisioning with rate limitingsequenceDiagram
actor Operator
participant daemon as Daemon
participant provisionAttempt as provisionRelaySecretAttempt
participant notify as ProvisionRelaySecret
participant relay as provisionViaRelay
participant serverbot as serverbot.Client
participant server as proxsave_server
Operator->>daemon: heartbeat loop
daemon->>daemon: maybeProvisionRelaySecret
daemon->>provisionAttempt: provisionRelaySecretAttempt(cfg, logger)
provisionAttempt->>notify: ProvisionRelaySecret(serverAPIHost, serverID, baseDir, logger)
notify->>relay: provisionViaRelay(ctx, serverAPIHost, serverID, logger)
relay->>serverbot: Client.Do(Request{POST /api/relay/provision})
serverbot->>server: POST /api/relay/provision {server_id}
alt 201 Created
server-->>serverbot: 201 {notify_secret}
serverbot-->>relay: Response{Status:201, Header, Body}
relay-->>notify: secret, alreadyProvisioned=false, nil
notify-->>provisionAttempt: (persist+confirm), nil
provisionAttempt-->>daemon: secret, retryAfter=0
else 200 OK already_provisioned
server-->>serverbot: 200 {status:"already_provisioned"}
serverbot-->>relay: Response{Status:200, Header, Body}
relay-->>notify: "", alreadyProvisioned=true, nil
notify-->>provisionAttempt: "", 0
provisionAttempt-->>daemon: "", retryAfter=0
else 429 TooManyRequests
server-->>serverbot: 429 Retry-After
serverbot-->>relay: Response{Status:429, Header, Body}
relay-->>notify: error RelayProvisionRateLimitError{RetryAfter}
notify-->>provisionAttempt: "", RetryAfter
provisionAttempt-->>daemon: "", RetryAfter
daemon->>daemon: provisionRetryAt = now + RetryAfter + daemonProvisionRetryJitter(serverID, RetryAfter)
else other status
server-->>serverbot: non-2xx
serverbot-->>relay: Response{Status, Header, Body}
relay-->>notify: error (unexpected status)
notify-->>provisionAttempt: "", 0
provisionAttempt-->>daemon: "", 0
end
Sequence diagram for post-upgrade what's-new screen invocationsequenceDiagram
actor Operator
participant upgradeSh as upgrade-beta.sh
participant proxsaveOld as proxsave(old)
participant proxsaveNew as proxsave(new)
participant whatsMode as runShowWhatsnewMode
participant whatsScreen as showWhatsnewScreen
participant dashboard as maybeShowWhatsnew
Operator->>upgradeSh: run upgrade-beta.sh
upgradeSh->>proxsaveOld: proxsave --upgrade --localfile
proxsaveOld->>proxsaveOld: upgradeFinalizePhase
alt upgradeErr == nil
proxsaveOld->>proxsaveNew: runWhatsnewAfterUpgrade(execPath, args)
proxsaveNew->>whatsMode: dispatchPreRuntimeModes (--show-whatsnew)
whatsMode->>whatsScreen: showWhatsnewScreen(ctx, args, toolVersion)
whatsScreen->>dashboard: maybeShowWhatsnew(ctx, session, baseDir, toolVersion)
dashboard-->>Operator: Screen 0 (what's new), then exit
else upgradeErr != nil
proxsaveOld-->>Operator: footer with error, no Screen 0
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
@CodeRabbit review |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
📝 WalkthroughWalkthroughThe change adds dedicated relay provisioning with parked-server recovery and rate-aware retries, introduces Screen 0 upgrade integration, consolidates notification failure logging, replays pre-viewport dashboard logs, and updates installation and task output. ChangesRelay recovery and provisioning
Screen 0 upgrade flow
Notification and interface output
Dashboard stream backlog
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant UpgradeFinalize
participant InstalledProxsave
participant WhatsnewScreen
UpgradeFinalize->>InstalledProxsave: invoke --show-whatsnew after successful upgrade
InstalledProxsave->>WhatsnewScreen: run interactive Screen 0 flow
WhatsnewScreen-->>InstalledProxsave: render or skip based on terminal and dry-run state
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/notify/webhook.go`:
- Around line 474-477: Demote the per-endpoint failure log in
WebhookNotifier.Send to Debug so it does not duplicate the
notification_adapter.go aggregate Warning. Keep the existing endpoint failure
details and control flow unchanged, with the adapter remaining the single
terminal user-visible failure log.
In `@internal/orchestrator/notification_adapter.go`:
- Around line 79-85: Update the complete-failure branch in the notification
adapter to avoid logging raw result.Error at Warning level. Keep the warning
message generic and, if diagnostic details are needed, emit the unsanitized
error only at Debug; preserve the existing generic warning when no error is
present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 97babcb1-0ab1-4f1d-9225-b499977c7fd4
📒 Files selected for processing (28)
cmd/proxsave/daemon.gocmd/proxsave/daemon_relay_provision_test.gocmd/proxsave/dashboard.gocmd/proxsave/main_modes.gocmd/proxsave/upgrade.gocmd/proxsave/whatsnew_upgrade_test.gointernal/cli/args.gointernal/cli/args_test.gointernal/health/config.gointernal/health/config_test.gointernal/notify/email.gointernal/notify/gotify.gointernal/notify/relay_provision.gointernal/notify/relay_provision_test.gointernal/notify/telegram.gointernal/notify/telegram_test.gointernal/notify/webhook.gointernal/orchestrator/healthcheck_setup_classify.gointernal/orchestrator/healthcheck_setup_test.gointernal/orchestrator/notification_adapter.gointernal/serverbot/client.gointernal/serverbot/client_test.gointernal/serverbot/request.gointernal/ui/components/streamtask.gointernal/ui/flows/install/audit.gointernal/ui/flows/install/audit_test.gointernal/ui/flows/install/install_test.goupgrade-beta.sh
gosec (G115, CWE-190) flags the uint64 -> int64 conversion in daemonProvisionRetryJitter. The value is provably bounded (the modulo keeps it below window <= 5min), but a structural fix is preferred over a #nosec: mask the FNV sum to 63 bits so it is always a valid non-negative int64, then take the modulo in signed space. Behavior is unchanged (deterministic, bounded jitter); only the conversion is made statically safe.
Raise coverage on the new POST /api/relay/provision client. Adds cases for the rate-limit error message (both branches + nil receiver), every parseRelayProvisionRetryAfter branch (absent, non-positive seconds, unparseable, integer seconds, and the HTTP-date future/past/beyond-cap paths), and the provisionViaRelay/ProvisionRelaySecret failure modes: unexpected status (body not leaked into the error), malformed 201 JSON, transport error, lock failure on a file baseDir, and a real read error on the under-lock re-check. relay_provision.go statement coverage rises from ~64% to ~98%.
Two related review findings on the single-terminal-voice logging refactor: - notification_adapter.go promoted result.Error to WARNING. That error can carry raw upstream response bodies (webhook.go embeds string(body) in its 400/403/5xx errors), so a warning could surface an arbitrary endpoint's reply in stderr and recorded logs. Keep the WARNING generic (as the fallback branch already is) and emit the unsanitized detail only at Debug. - webhook.go logged each failed endpoint at Error in the caller loop while the adapter warns once on the aggregate, double-reporting a single failure. Demote that per-endpoint line to Debug so the adapter stays the single terminal voice (this also stops the wrapped raw body from surfacing above Debug here).
…l upgrade The Screen 0 hook gated on upgradeErr == nil (binary install) alone, so a good binary install followed by a FAILED config upgrade still opened the celebratory what's-new screen while the footer showed 'Configuration: ERROR' and the command returned a nonzero exit. Require cfgUpgradeErr == nil too. Update the structural AST guard to pin the two-condition gate.
Rewrite the two-condition gate matcher from !((A && B) || (C && D)) to the De Morgan form (two named booleans, !forward && !reverse) so staticcheck's QF1001 quickfix is not triggered. No behavior change.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/proxsave/whatsnew_upgrade_test.go`:
- Around line 38-39: Rewrite the compound condition around isErrNilCheck so it
uses the De Morgan–simplified form or clear named predicates, while preserving
the requirement that land.X and land.Y contain upgradeErr and cfgUpgradeErr in
either order. Ensure the resulting expression satisfies golangci-lint QF1001.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e1f3729a-77ce-4771-a8c9-d1db6455e793
📒 Files selected for processing (6)
cmd/proxsave/daemon.gocmd/proxsave/upgrade.gocmd/proxsave/whatsnew_upgrade_test.gointernal/notify/relay_provision_test.gointernal/notify/webhook.gointernal/orchestrator/notification_adapter.go
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/proxsave/upgrade.go
- cmd/proxsave/daemon.go
Greptile flagged that ProvisionRelaySecret silently returns success when the server answers already_provisioned while the local secret is gone, leaving each retry stuck. The server already re-mints for an UNCONFIRMED row (201), so this branch is specifically the confirmed-then-lost case: the server keeps only the secret's sha256 and cannot return the plaintext, and re-minting a confirmed token for an unauthenticated caller would let anyone knowing the 16-digit server_id rotate another host's secret. There is no safe in-band recovery, so raise a WARNING with the remediation (recovers automatically once the chat-less row becomes purge-eligible, or an operator clears it) instead of a silent Debug. Assert the warning fires in the already_provisioned test.
…t from orchestrator init A backup launched from the interactive dashboard streamed its log into a contained viewport that started mid-run at "Initializing backup orchestrator", losing every line logged before the viewport existed: the version banner, environment detection, config load, "Log file opened", server identity, and the security preflight. Those lines are on disk via two logger paths - logWithLabel (console+file) and AppendRaw (file-only, used by bootstrap.Flush for the raw banner) - but the viewport only tapped the console, which the dashboard handoff mutes and which is captured only once backupStreamSteps begins. Add a colored `mirror` sink on Logger, written on BOTH logWithLabel and AppendRaw, parallel to the file sink and independent of the muted console. initializeRunLogger attaches it (into a bounded LineBacklog) on a dashboard handoff, before bootstrap.Flush, so it captures the whole pre-viewport stream in file order. runBackupStreamed replays that backlog into the panel and detaches the mirror before the live capture is installed, so no line is double-captured. The viewport now shows the same complete, ordered, colored stream the log file has. CLI/cron/daemon runs are unchanged (mirror never attached; nil-guarded).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/proxsave/backup_stream_test.go`:
- Around line 574-578: Update the test around the three waitFor calls to assert
ordering as well as presence: after all expected strings are available, obtain
each substring’s index in buf and require the ProxSaveBanner index to precede
Environment: dual, which must precede Initializing backup orchestrator.
In `@cmd/proxsave/backup_stream.go`:
- Around line 55-61: The mirror-to-live-capture handoff in captureRunOutput
currently leaves a gap where concurrent logger output is discarded. Serialize or
atomically coordinate clearing the mirror, replaying backlog.Lines(), and
installing the live capture so each line is delivered exactly once to either the
replay sink or viewport, with no interval using io.Discard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: cd8e418c-d2d3-4907-91b9-4b1cb24f82e5
📒 Files selected for processing (8)
cmd/proxsave/backup_stream.gocmd/proxsave/backup_stream_test.gocmd/proxsave/main_runtime.gocmd/proxsave/whatsnew_upgrade_test.gointernal/logging/capture.gointernal/logging/logger.gointernal/notify/relay_provision.gointernal/notify/relay_provision_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
- cmd/proxsave/whatsnew_upgrade_test.go
- internal/notify/relay_provision.go
- internal/notify/relay_provision_test.go
| // Detach the mirror while the console is still muted and before the live pipe | ||
| // exists: from here the run's remaining lines reach the panel only through the | ||
| // capture the caller installs next, so nothing is double-captured. | ||
| logging.GetDefaultLogger().SetMirror(nil) | ||
| for _, line := range backlog.Lines() { | ||
| emit(line) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Prevent log loss during the mirror-to-live-capture handoff.
After Line 58 clears the mirror, the logger still writes to io.Discard until captureRunOutput is installed at Line 164. Any concurrent log in that interval is absent from both the replay and the viewport. Make this transition atomic/serialized so every line reaches exactly one sink.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/proxsave/backup_stream.go` around lines 55 - 61, The
mirror-to-live-capture handoff in captureRunOutput currently leaves a gap where
concurrent logger output is discarded. Serialize or atomically coordinate
clearing the mirror, replaying backlog.Lines(), and installing the live capture
so each line is delivered exactly once to either the replay sink or viewport,
with no interval using io.Discard.
… present TestRunBackupStreamedReplaysPreStreamBacklog waited for the three substrings (banner, environment, orchestrator init) with presence-only polls, so a regression that streamed the live step line before the replayed backlog would still pass. Add an index comparison requiring banner < environment < init on the captured frame, matching the on-disk log order the fix guarantees.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cmd/proxsave/backup_stream_test.go (1)
588-588: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winResolve the QF1001 finding.
Rewrite the negated conjunction using De Morgan’s law; this preserves behavior and clears the reported staticcheck finding.
Proposed fix
- if !(iBanner >= 0 && iBanner < iEnv && iEnv < iInit) { + if iBanner < 0 || iBanner >= iEnv || iEnv >= iInit {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/proxsave/backup_stream_test.go` at line 588, Update the condition involving iBanner, iEnv, and iInit to apply De Morgan’s law instead of negating the conjunction, preserving the same boolean behavior while resolving the QF1001 staticcheck finding.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cmd/proxsave/backup_stream_test.go`:
- Line 588: Update the condition involving iBanner, iEnv, and iInit to apply De
Morgan’s law instead of negating the conjunction, preserving the same boolean
behavior while resolving the QF1001 staticcheck finding.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 07ec2ede-057c-49d5-af52-e48fca0b5c63
📒 Files selected for processing (1)
cmd/proxsave/backup_stream_test.go
…QF1001) The negated compound condition tripped golangci-lint QF1001. Rewrite !(a && b && c) as the equivalent disjunction so CI passes; behavior unchanged.
| if logger != nil { | ||
| logger.Warning("Relay provisioning: server_id is already provisioned server-side " + | ||
| "but no local relay secret is present; a confirmed token cannot be re-minted " + | ||
| "automatically. Centralized healthcheck config fetch and relay notifications stay " + | ||
| "unavailable until the server-side row is purged and recreated (automatic once the " + | ||
| "row is purge-eligible) or cleared by an operator.") | ||
| } | ||
| return false, nil |
There was a problem hiding this comment.
Provisioned secret remains unrecoverable
When the local relay secret is missing while the server retains a confirmed registration, this branch logs a warning and returns without restoring credentials or initiating recovery. Each daemon retry receives already_provisioned and reloads the still-empty secret, leaving centralized healthchecks and relay notifications unavailable until a server-side purge or operator intervention.
Automated release PR for
v0.30.0-beta7.Summary by Sourcery
Switch relay secret provisioning and self-heal to a dedicated /api/relay/provision endpoint with rate-limit backoff, enhance daemon and healthcheck handling for parked hosts, and add a post-upgrade flow that shows release notes from the newly installed binary.
New Features:
Enhancements:
Tests:
Summary by CodeRabbit
--show-whats-newmode to display release highlights and exit.Greptile Summary
This release updates relay provisioning and parked-host recovery, adds post-upgrade release notes, replays dashboard log backlogs, and refines notification logging and install UI behavior.
/api/relay/provisionendpoint with typed rate-limit backoff.--show-whatsnewhandoff after successful interactive upgrades.Confidence Score: 4/5
The PR is not yet safe to merge because losing a local secret for an already-provisioned host still leaves relay notifications and centralized healthchecks unavailable indefinitely.
The updated branch detects and warns about the orphaned confirmed registration but neither restores credentials nor initiates a recovery transition, so daemon retries continue returning no usable secret.
internal/notify/relay_provision.go
Important Files Changed
already_provisionedstate still has no client-driven recovery.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Daemon needs relay secret] --> B[POST /api/relay/provision] B -->|201 with secret| C[Persist and confirm secret] B -->|429| D[Schedule server-directed retry] B -->|200 already_provisioned| E[Warn and return without secret] E --> F[Centralized fetch remains unavailable] F --> G[Retry after local throttle] G --> BReviews (7): Last reviewed commit: "test(dashboard): apply De Morgan to the ..." | Re-trigger Greptile