Repository navigation
feat: Slack on-call mentions, paging coverage, worker resilience, PROXY protocol and org-relative private regions - #215
Merged
Merged
Conversation
…h guard is flaky in CI
The guard at live-updates.spec.ts counted 3 list fetches where 1 was
allowed. Instrumenting the counted requests with their query string and
their offset from the moment the counter is armed shows it is the
MEASUREMENT WINDOW, not a refetch storm:
[diag] listFetches=1
+302ms ?with=last_result&q=E2E+Steady+State+…&limit=100&sort=group
[diag-after-transition] listFetches=3
+302ms ?with=last_result&q=E2E+Steady+State+…&limit=100&sort=group
+8970ms ?limit=10
+8970ms ?with=last_result&q=E2E+Steady+State+…&limit=100&sort=group
Three defects add up to exactly the CI number (1 + 2 = 3):
1. The counter was armed before the search debounce fired. `fill()` only
sets the input; 300 ms later the `q=` query key changes and fetches.
The row was already visible from the PRE-FILTER list, so the
visibility assertions settled sooner than that fetch — which then
landed inside the window and ate the entire budget of one.
2. The counter matched by URL pathname, so it also counted the org
layout's CommandMenu query (`useChecks(org, {limit: 10})` — the
`?limit=10` line). Both it and the page's infinite list match the
checks/checks live roots, so ONE invalidation cost TWO.
3. The window was not immune to a genuine `checks` transition, and the
test manufactured one itself: a heartbeat check is scheduled like any
other (default period 60 s) and its passive job writes a
`No heartbeat received` → down result. Its first run fires within ~2 s
of creation and races the setup heartbeat — locally 1 run in 8 was
already `down` before the window opened (failing on the Up badge,
screenshot: `heartbeat-heartbeat Down 0ms`). Under CI load that run is
delayed into the window, flips up → down, and publishStatusHint →
PublishImmediate(KindChecks) invalidates both list roots: the +2.
The polling boundary is ruled out: CHECKS_LIST_POLL_MS is 10 s and every
counted request is timestamped; across 8 local runs no poll tick ever
landed in the ~6 s window.
Fixes, all in the test:
- create the check with a 1 h period so its passive job cannot re-fire
inside the window, and settle it at `up` through a new
waitForCheckStatus() barrier that re-sends the heartbeat until the
server agrees (also fixes the second, independent Up-badge flake; used
by the sibling checks-list test too);
- arm the counter only after the filtered list response has landed;
- count only this page's list query (`q=<check name>`), never the
CommandMenu's;
- tally `checks`-kind hints on the live socket and allow one refetch per
damper interval that could have carried one, so a foreign transition in
the shared test org is accounted for rather than silently fatal;
- assert the positive control explicitly: the final genuine transition
must still produce at least one list fetch, so the budget check can
never pass by counting nothing.
10/10 green with --repeat-each=10 --workers=1 against a CI-equivalent
side-car test server.
waitForCheckStatus() converged on `up` but did not prove it would stay there: the check's first passive job runs within ~1 s of creation and its read of the last result can predate the setup heartbeat, so it writes a `No heartbeat received` → down result *after* the barrier already saw `up`. That reproduced as 1 failure in 20 (`… heartbeat-heartbeat Down 0ms` on the Up-badge assertion). The barrier now takes a stability window and only returns once the status has held for it, re-sending the heartbeat whenever it has not. Combined with the steady-state check's 1 h period, a 3 s hold outlasts the single passive run and the next one is an hour out — the status is settled for the rest of the test, so no transition (and no legitimate `checks` hint) can still be in flight when the counter is armed. 20/20 green with --repeat-each=10 --workers=1 across both checks-list live tests.
…he steady-state refetch guard
…ard is flaky in CI
…rd is flaky in CI
CheckGroupSection folded page-level hasNextPage/isFetchingNextPage into its per-group isLoading, so an empty group (checkCount=0) read as "still loading" for as long as the infinite-scroll stream had more pages to fetch -- effectively forever on orgs with many checks. Use the authoritative group.checkCount to short-circuit straight to the empty state when not filtering; keep deferring to the stream while filtering, since checkCount is the unfiltered total. Also adds a "Add a check to this group" link (pre-filled group) to the empty state for the unfiltered case.
Playwright coverage for spec 2026-08-12-02: mocks /check-groups and the paginated /checks endpoint so a group with checkCount 0 and a group that's fully loaded on page 1 coexist with hasNextPage staying true (simulating an org with more checks than one page). Asserts the empty group shows "No checks in this group" plus the new "Add a check to this group" link immediately, never skeletons, and that the non-empty group's rows still render fine (regression guard for the existing "rows/skeletons while streaming" behavior).
…an endless skeleton instead of an empty state
…n endless skeleton instead of an empty state
…all person, and members have no integration identity mapping
…the on-call person, and members have no integration identity mapping
…nt.escalated alerts
…up and first-class Slack row
…nd on-call warning
Context-first test helpers, range-index loops over copied structs, strings.Builder accumulation, shadowed-err renames and a split of the members error mapper and the Slack escalation builder into per-family helpers. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on-call person, and members have no integration identity mapping Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… number The duplicate guard in AddMemberContact read the ROUTE-JOINED contact list, so a live contact with no route was invisible to it. The add then fell through to the upsert, which matched the (user, org, type, value) unique key and landed on that very row, and the unconditional verified-stamp clear wiped a stamp the member had earned — quietly making them unpageable on a number that worked. A contact goes routeless whenever the member drops the route but keeps the number, so this is an ordinary state, not a corrupt one. The guard is now a type/value lookup (findLiveContact) that sees every live row regardless of routing, and refuses it like any other pre-existing contact. The unconditional clear stays: it still covers the soft-delete resurrection path, where the upsert un-deletes a row that may carry an old verified stamp. Also surfaces the email-only warning badge on the on-call schedule DETAIL page roster, which rendered plain names with no coverage signal — reusing the existing EmailOnlyBadge and coverage hook, no new component and no new i18n keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on-call person, and members have no integration identity mapping Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n-call person, and members have no integration identity mapping
…ng search Verified at 375px width: the search+refresh+new-token+docs-link row had no flex-wrap, so the new DocsLink icon squeezed the search input down to a few visible characters. Wrapping the row (and giving the search box a min-width) fixes this — see screenshot verification in the implementation notes.
…ks across the dashboard
…s across the dashboard
The private-locations doc told operators to run `base64`/`kubectl cp` inside the agent container to recover SP_AGENT_KEYS, but the shipped runtime is gcr.io/distroless/base-debian13:nonroot — no shell, no base64, no tar — so every kubectl path silently failed and pushed operators toward SP_AGENT_PRINT_KEYS, which leaks private key material into the log pipeline. Verified against the real ghcr.io/fclairamb/solidping:latest image: `docker exec ... base64` and `/bin/sh` both fail with "executable file not found"; a filesystem export confirms no base64/tar/sh/busybox anywhere in the image; `docker cp <container>:path -` still works (daemon-side, no exec) and the alpine-helper-container base64 command works against the named volume. Rewrites the extraction section to lead with "keep it in the pod" and reworks the Kubernetes section into two options, PVC-backed identity first (with the fsGroup:65532 / Recreate / optional enrollment-Secret details a real production deployment needed) and the SP_AGENT_KEYS + Secret alternative kept honest about how the value is sourced.
… wiki Same bug as private-locations.md: the internal "Bootstrapping SP_AGENT_KEYS" note told engineers to exec base64/kubectl cp inside a distroless container. Adds the internal why (distroless is a deliberate security choice; SP_AGENT_PRINT_KEYS must not become the standard k8s bootstrap) and points at the PVC pattern our own production deployment uses, cross-linking the public doc's manifest instead of duplicating it.
deploy/fly/README.md already correctly documented the image as distroless with no /bin/sh (for the wrapper-entrypoint note) but still told operators to run `fly ssh console -C "base64 -w0 ..."` a few lines above it — self-contradictory, and doubly broken since fly ssh console also needs hallpass, which this image doesn't carry. Replaces it with the same last-resort SP_AGENT_PRINT_KEYS guidance used in the other two docs.
…un base64 inside a distroless image, which cannot work
…n base64 inside a distroless image, which cannot work
fclairamb
force-pushed
the
batch/2026-08-12
branch
from
August 13, 2026 18:36
f2f0313 to
9d48bdf
Compare
fclairamb
enabled auto-merge (squash)
August 13, 2026 18:38
Link to the parent /orgs/$org/organization and /orgs/$org/account routes (not their current redirect targets) so the palette keeps working if the default child route changes. New nav.organization and nav.account i18n keys across en/fr/de/es, distinct from the existing group-heading keys.
…directs Search hits by "organization"/"account", plus assert the resulting navigation lands on the current redirect target (invitations/profile) so the parent-path-plus-redirect contract stays proven.
…enu pages test
The new Organization/Account entries share their title text with the
group heading rendered above them ("Organization" heading, "Organization"
entry), which broke the existing exact-text assertion under strict mode.
Scope those assertions to [cmdk-group-heading] and assert the new
entries by testId alongside the pre-existing ones.
…the Organization and Account landing pages
…he Organization and Account landing pages
Merged
…rnetes The previous revision fixed the broken `base64` extraction commands but concluded that "Kubernetes has no in-cluster extraction path, full stop", and recommended a PVC-backed identity on that basis. The premise is wrong: enrolling in a throwaway Pod where an alpine sidecar shares an emptyDir with the agent makes the keys file readable without executing anything in the distroless container. Verified in production while standing up the @stonal/s3ns-paris agent on GKE Autopilot. So the Kubernetes section now leads with the stateless pattern — identity in a Secret, injected as SP_AGENT_KEYS, no volume — structured as enroll → capture → run, and pipes the value straight from the sidecar into the Secret so the key material never reaches a terminal, a shell history or a log. The PVC keeps its place as the alternative for people who would rather the identity never leave the cluster, with its trade-offs stated: node-pinned, ReadWriteOnce, unrecoverable if lost. Also documents SP_NODE_NAME, without which the worker slug is derived from the truncated pod hostname and every restart lands on a new workers row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fclairamb
added a commit
that referenced
this pull request
Aug 13, 2026
🤖 I have created a release *beep* *boop* --- ## [0.14.0](v0.13.0...v0.14.0) (2026-08-13) ### Features * Slack on-call mentions, paging coverage, worker resilience, PROXY protocol and org-relative private regions ([#215](#215)) ([eaa4236](eaa4236)) ### Bug Fixes * **deps:** update module golang.org/x/net to v0.58.0 ([#213](#213)) ([0b2a7c9](0b2a7c9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
…PVC option Supersedes the PVC-first guidance introduced by spec 2026-08-13-05. That spec claimed Kubernetes had no in-cluster extraction path and recommended a PVC on that basis; the claim was wrong. Extraction works fine through a throwaway enrollment Pod that shares an emptyDir with an ordinary alpine sidecar — the distroless constraint never applies to the sidecar, which has a shell and base64 and reads the file through the shared volume. With extraction available, a PVC buys nothing: it pins the pod to a ReadWriteOnce volume (forcing strategy: Recreate), makes an otherwise stateless agent stateful, and turns ordinary volume loss into unrecoverable identity loss. The supported shapes are now exactly two — SP_AGENT_KEYS from an env var, or from a Secret — and the steady-state Deployment needs no volume, no fsGroup and no Recreate strategy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-time module check-summary-cards.tsx consumes the shared module instead of defining its own copy, so there is a single implementation of the live-ticking duration logic ahead of reusing it on the private-locations agents table.
Replace the raw toLocaleString() cell in the private-locations agents table with LiveDurationAgo (shared with check-summary-cards), so it reads as '5m ago' and ticks live instead of forcing a mental timestamp diff. The exact local timestamp stays reachable via a title tooltip on hover, and the existing 'never' fallback for agents with no lastSeenAt is preserved. Also registers the shared LiveDurationAgo pattern in the design reference so it stays the canonical example for future live-relative-time UI.
Stub the agents list (a real agent connection isn't practical from Playwright, same rationale as deported-agent-wizard.spec.ts) with one agent that has lastSeenAt and one without. Assert the cell matches /ago/ and carries a non-empty title tooltip, and that the no-lastSeenAt row still reads "never" — never assert an exact relative value since it ticks every second.
…ow a relative time in local time
…w a relative time in local time
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.
Summary
Ten specs, each implemented in isolation and gated behind an independent completeness audit before being archived. Four needed a fix round; nothing was archived on a passing build alone.
Notifications
user_integration_identitiestable maps org members to Slack users per workspace, auto-matched by email after OAuth with a manual override. Mentions cover every human target on escalation step 1, deduplicated; resolved/reopened stay mention-free; a missing identity degrades to plain text and never blocks the send. Defaults on for newly created Slack integrations, off for existing ones.Reliability
db/dbfault, surfaced as a 503 and then a clean shutdown. Transient faults are unchanged and still retry.Agents / networking
Dashboard
regionparam on check detail (no reload on change), live relative "Last seen" for private-location agents, and an empty-state fix for check groups with zero checks.Docs
base64-inside-the-container commands cannot work. Now documents the two supported shapes (SP_AGENT_KEYSvia env var or Secret) and a working sidecar extraction route, instead of leavingSP_AGENT_PRINT_KEYSas the de-facto path.Notable for review
011_v0_14_0rewrites existing rows across agents, checks, jobs, org defaults, enrollment tokens and results. Idempotent, dedupes before rewriting to respect two unique indexes, verified against real Postgres. The release cycle's scratch migrations were consolidated into this single file perwiki/conventions/database.md, with a golden-schema diff confirming the result is byte-identical on both engines.No breaking API changes.
Test plan
make build-backend lint-back testgreenmake build-dash0+ eslint, no new errors over the known baselinemake build-docsgreen, no broken links