Repository navigation
feat: onboarding checklist, default email alerts, and one-step status pages - #282
Merged
Merged
Conversation
…e specs Tracked so each archive step can git mv rather than leaving a stale duplicate in specs/todos/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Go's x/crypto/ssh client never attempts an auth method the server doesn't advertise. Servers that offer only keyboard-interactive (a common sshd ChallengeResponseAuthentication configuration, e.g. test.rebex.net as of 2026-08-25) reject a client offering only ssh.Password, having attempted nothing. Add a shared internal/sshauth.PasswordMethods(password) helper that returns both ssh.Password and ssh.KeyboardInteractive (answering every prompt with the password, empty slice for zero-question banners) and wire it into the three password-auth call sites: checksftp, checkssh's executeWithAuth, and sshtunnel. checkssh's verifyFingerprint probe (deliberate empty-password handshake to capture a host key fingerprint) is untouched.
…sshtunnel Adds, for each of the three fixed call sites, an in-process x/crypto/ssh server whose ServerConfig sets ONLY KeyboardInteractiveCallback (no PasswordCallback), asserting successful auth with a configured password; a companion password-only server as a positive control; and a wrong- password negative test proving no silent success. - internal/sshauth: unit tests on PasswordMethods' method-list shape plus its keyboard-interactive callback's answer-count contract (zero questions -> empty slice, N questions -> N copies of the password), and SSH-layer handshake tests against both server shapes. - internal/checkers/checkssh, internal/checkers/checksftp: same three cases driven through the real Execute() entry point (checksftp wires a minimal github.com/pkg/sftp server-side subsystem so sftp.NewClient completes). - internal/integrations/sshtunnel/sshtunneltest: new StartKeyboardInteractiveOnly bastion variant, reusing the existing Start() scaffolding, exercised through the real sshtunnel.Dial / Resolve path in sshtunnel_test.go.
…ainst servers that only offer keyboard-interactive SSH auth
…inst servers that only offer keyboard-interactive SSH auth
The single- and multi-series activeDot renderers hardcoded payload.status === "down" (missing error/timeout), and the non-active dot renderers duplicated isFailingStatus's own logic inline instead of calling it. Route all four through the shared isFailingStatus() helper so dot color always agrees with the line gradient.
Consolidate the four inline isFailingStatus(status) ? DOWN : neutral ternaries (single/multi-series x dot/activeDot) into one exported dotFillColor() helper, so tests can assert against the exact function every renderer calls instead of a re-implementation of it.
…degraded Asserts dotFillColor() renders down/error/timeout/unknown red (matching the line gradient) and warning/degraded/abandoned/up neutral, plus a source guard that no fill decision in response-time-chart.tsx bypasses dotFillColor() with a re-implemented payload.status comparison.
…ders error/timeout points green on a red line
…ers error/timeout points green on a red line
… rows until the 7-day GC — supersede them at enrollment
…uplicate rows until the 7-day GC — supersede them at enrollment
A system agent booting without a pinned identity enrolls anew every time, so every pod restart or redeploy left the previous row behind until the agent_gc job's 7-day silence window (seven rows for one deployment, in production). Retire the predecessors inside the enrollment transaction instead: same kind='system', region and name, and only when last_seen_at proves the row is not a live fleet peer (15-minute disconnect window). Org agents are never candidates. db.RetireAgentWorkerRows is the factored-out cleanupWorkerRow the GC and this path now share.
rescheduleSelf passed a nil config, so an operator override of StaleAfterSeconds / RevokedPurgeAfterSeconds survived exactly one sweep and then silently reverted to the defaults. Also routes the worker-row cleanup through the shared db.RetireAgentWorkerRow.
…g reschedule The supersede matrix runs one body against in-memory SQLite and embedded PostgreSQL: stale same-(region,name) predecessor retired with its worker row, a same-name row with a FRESH last_seen_at survives (the positive control for the disconnect guard), different name / different region / org agent all survive, and an org enrollment supersedes nothing. Both new tests were confirmed to fail against the pre-fix code.
…cate rows until the 7-day GC — supersede them at enrollment
…ate rows until the 7-day GC — supersede them at enrollment
…t, so everyone still believes the first responder has it Three questions blocked the batch. Recorded in the spec itself, since the implementer reads the file and never sees the conversation. - Unack resumes escalation from the step the ack interrupted, option (c) — NOT the spec's own suggested (a) default. The notice must therefore say escalation resumes, not that paging has stopped. If the interrupted step index is not persisted today, persisting it is part of the work. - Comments forward per-comment and immediately, accepting the notification noise the original scope call avoided. - The Discord message id question is answered from the source rather than left open: discord.go already persists it (:32, :211, :247, :317), so A.1 needs no storage change for Discord. Also tracks the three new specs so each archive step can git mv them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… search page Chips for selected checks that fall outside the current 25-result search page rendered raw UUIDs instead of names — the common case on an edit form loading a persisted selection with more than ~25 org checks. Add useChecksByUids (hooks.ts), which resolves each unmatched uid individually via useQueries, sharing useCheck's ["check", org, uid] cache key/fetch. CheckMultiPicker now keeps an accumulating sticky label map so a name, once resolved, never regresses to "…" or a UUID as the user narrows their search — mirroring CheckPicker's single-value fallback (name || slug || uid, "…" while resolving, uid only on a genuine fetch error). Only wired for kind="checks"; groups are unaffected (fetched whole already).
Unit tests for the fix in the previous commit: a selected check outside the current search page resolves its name via the individual fetch (with "…" while pending and uid-on-error fallbacks), a name already resolved stays sticky across a narrower search that drops the check from checkMatches, and kind="groups" never triggers per-uid fetches.
…h page Playwright regression for spec 2026-08-28-05: seeds a target check plus 30 newer checks (default listing is created_at DESC, so the target is guaranteed to fall outside the 25-result default page), selects it on a report schedule and a maintenance window via the API, then asserts the edit page's chip shows the check's name with a negative control that no chip text matches a UUID pattern. Authored but not run locally: the :4000 devloop is not SP_RUNMODE=test, which this suite requires (per web/dash0/CLAUDE.md / make test-dash).
…or checks outside the current search page
…r checks outside the current search page
… ramp CheckRow and SLORow gained a PascalCase-tagged URL field (dash0 detail page, built the same way job_escalation_step.go does), and CheckRow/Data gained AvailabilityColor — a hex color piecewise-interpolated across the product's existing 99.9/99.0/98.0 thresholds rather than linearly over 0-100, so 99.0 and 99.9 no longer render as the same shade of green. SLO rows deliberately get no color: they already carry a target-driven Healthy/At-risk/Breached badge, which would disagree with an absolute gradient.
…me report The per-check and per-objective tables now link the name to its dash0 page when a URL is available (falling back to plain text otherwise, never a half-built href=""), and the per-check + headline availability figures carry an inline color driven by the new AvailabilityColor field. The plain-text part appends the URL as a trailing line per row; color stays HTML-only.
- availabilityTextColor: pin the anchor colors (>=99.9, 99.0, <=95), prove a midpoint actually interpolates, and sweep the full range to confirm a higher percentage never renders redder. - render_test.go: assert check/objective names link to their dash0 pages in both HTML and text, that an empty base URL renders plain names with no href="", and that only HasData rows get the colored value span.
…names checks and objectives without linking to them, and renders every availability number in the same neutral color Gate run by the coordinator: the implementing subagent ended its turn while still waiting on a backgrounded run and never landed this marker, twice over. Verified independently instead — make build-backend OK, make lint-back '0 issues.', and go test ./internal/uptimereport/... ./internal/email/... green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ames checks and objectives without linking to them, and renders every availability number in the same neutral color
…kes three separate manual steps
…es three separate manual steps
… user to alerting, weekly reports, or a status page
…es a new user to alerting, weekly reports, or a status page
… new user to alerting, weekly reports, or a status page
…new user to alerting, weekly reports, or a status page
…n errors POST /auth/register and AcceptInvite with a too-short password (and CreateInvitation with an invalid role) fell through to WriteInternalError, answering 500 INTERNAL_ERROR for a user typo and paging Sentry as if it were a server fault. handleRegistrationError and handleInvitationError now map ErrInvalidCredentials to 400 VALIDATION_ERROR, mirroring the existing handleChangePasswordError/handlePasswordResetError pattern. Sweep of the other handle*Error helpers in this package found three more instances of the same gap and fixed them the same way: - handleAuthError (Login/SwitchOrg): SwitchOrg's ErrUserNotFound now folds into the existing anti-enumeration case. - handleRefreshError: Refresh's ErrUserNotFound (user deleted after token issuance) now maps like any other invalid refresh token. - handle2FAError: completeLoginAfter2FA's ErrOrganizationNotFound (org deleted/renamed mid 2FA-challenge) is now mapped instead of 500ing.
…ndings Route-level tests for POST /auth/register and AcceptInvite with a too-short password (400 VALIDATION_ERROR, detail mentions the 8-character minimum), plus direct unit tests of handleAuthError, handleRefreshError and handle2FAError with the three sentinels found during the handler sweep (ErrUserNotFound, ErrUserNotFound, ErrOrganizationNotFound).
…rns 500 INTERNAL_ERROR instead of 400 VALIDATION_ERROR
…ns 500 INTERNAL_ERROR instead of 400 VALIDATION_ERROR
PageHeader renders the SLO name as an <h1>, and the status card's
CardTitle (an <h3>) echoes it too — but only once the SLO has a computed
attainment window. A brand-new SLO has none, so the ambiguous
getByRole("heading", { name }) locator only ever resolved to one element
by accident. This batch's longer E2E run lets the seeded check accumulate
results before slos.spec.ts runs, so the status card renders and the
locator hits Playwright's strict-mode violation. Scope to level: 1 so the
assertion holds whether or not the status card is present.
The Command Menu's status-page/escalation-policy/SLO search shares its query keys (["statusPages", org], etc.) with other consumers, and the app's default staleTime is 60s. The dashboard's getting-started checklist (OnboardingChecklist) renders on every dashboard load and calls useStatusPages before the Command Menu ever opens, seeding that cache entry. When a user then creates a status page (via the API, another tab, or the create flow itself) and immediately searches for it with ⌘K, React Query serves the checklist's stale snapshot instead of refetching — the new page is invisible in search for up to a minute even though it already exists server-side. Give useStatusPages/useEscalationPolicies/useSlos an opt-in `staleTime` (mirroring the existing ListQueryOptions pattern used by useIntegrations, useReportSchedules, useMembers, etc.), and have CommandMenu request staleTime: 0 for all three entity-search queries so opening the palette and searching always reflects the latest data, regardless of what another component happened to cache. Reproduced by running command-menu.spec.ts's "searches status pages and navigates to the matching one" test in isolation against unmodified code — it fails deterministically because the dashboard's checklist always primes the cache before the test creates its status page, not because of leakage from other spec files.
…from-check.spec This spec created a status page and a check via the UI flow but never deleted either, leaking them into the shared "test" org on every run. A leaking spec makes the suite order-dependent by construction; wrap the body in try/finally and delete both, mirroring the cleanup pattern already used by command-menu.spec.ts and the other status-page specs.
…it form The edit route fetches the window's own fields (useMaintenanceWindow) and its check/group associations (useMaintenanceWindowChecks) as two independent queries, but only gated the loading skeleton on the first one. MaintenanceWindowForm seeds its checkUids/checkGroupUids state from `initialChecks` with a plain useState initializer, which only runs once at mount and is never resynced when the prop changes later. So whenever the checks query was still in flight when the window query resolved, the form mounted with an empty selection and permanently dropped the persisted checks from the picker — surfaced as a flake in check-multi-picker-uuid-chips.spec.ts's maintenance-window test (observed once during a full-suite run, most likely under the elevated latency right after that test's 31 concurrent check creations; not reproducible in isolation across several clean re-runs). Gate isLoading on both queries so the form never mounts before both have resolved.
Merged
fclairamb
added a commit
that referenced
this pull request
Aug 29, 2026
🤖 I have created a release *beep* *boop* --- ## [0.20.0](v0.19.1...v0.20.0) (2026-08-29) ### Features * onboarding checklist, default email alerts, and one-step status pages ([#282](#282)) ([289478e](289478e)) ### Bug Fixes * **deps:** update go dependencies (non-major) ([#279](#279)) ([4cbcc29](4cbcc29)) * **deps:** update go dependencies (non-major) ([#283](#283)) ([9709152](9709152)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This was referenced Sep 8, 2026
Closed
Closed
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.
Batch branch covering 17 specs. Every spec was implemented, gated on its own scoped build/lint/test run, and then cleared by an independent completeness audit before being archived to
specs/done/.CreateOrgseeds anisDefaultemail integration addressed to the owner and an org-wide weekly uptime report. Existing orgs are untouched (no migration, no retro-seeding), and the bootstrap default org and the test-mode org are excluded structurally — neither goes throughCreateOrg, which is what keeps thousands of self-hosted instances from mailingadmin@solidping.io. Seeding is best-effort: a failure logs a WARN and never fails signup.POST /checks/validatenow rejects payloads it previously accepted. That is the fix — it was answering{"valid": true}for bodies thatPOST /checksthen 422'd. Create's 422 shape, field names, messages and status codes are unchanged.Features
FirstResultCelebrationbanner and its localStorage key. Dismissal moves to new per-userGET/PUT/DELETE /api/v1/me/ui-state/:keyendpoints (key allowlist, 4 KiB cap, per-user isolation), so it works across devices and can be re-enabled from the account page.CreateStatusPageacceptscheckUidsand lands page + section + resources atomically in one transaction across both DB dialects.Fixes
VALIDATION_ERROR. A sweep of the sibling error helpers found three more unmapped sentinels of the same class./pub/assets/…path landed in atype="url"input, so native validation blocked every save, including unrelated name/slug edits. Upload and external-URL are now distinct sources./auth/merefresh clobbering in-flight name/slug edits — edit the slug right after saving the name and your edit silently reverted. Fixed with touched-field guards.Verification
Backend build/lint/test, dash0 build/lint, 1121 unit tests, and scoped Playwright E2E per spec. New user-facing strings are in all four locales with real translations.