Skip to content

Batch 2026-08-26-2: rate caps & scheduling, real availability, entitlements editor, Gotify/Zulip, support preflight - #269

Merged
fclairamb merged 146 commits into
mainfrom
batch/2026-08-26-2
Aug 27, 2026
Merged

fclairamb merged 146 commits into
mainfrom
batch/2026-08-26-2

Conversation

@fclairamb

@fclairamb fclairamb commented Aug 27, 2026 •

Copy link
Copy Markdown
Owner

Integration branch for the 2026-08-26 spec batch (second run), plus follow-up fixes.

Highlights

Rate limits & fair rotation

  • Over-cap deferrals now preserve effective_scheduled_at and rotate the deficit fairly across the org's checks instead of starving the same ones.
  • Daily rate-limited-skip counter, plus checksPerMinute demand/limit and daily skips exposed on the entitlements payload.
  • Orgs over their cap get a "Some check executions are being skipped" warning banner on the checks list, scheduling and Usage pages — with an Upgrade CTA when the deployment configures a SaaS upgrade URL.

Check scheduling page

  • New /checks/scheduling page that brings an org back under its per-minute rate cap: auto-rebalance proposal, a meter anchored on the server's own demand figure, a phone-usable table, and the period select floored at the server's 10s minimum.

Internal-checks quota loophole

  • A client-supplied internal flag is refused on every check write path (create, update, import), OpenAPI no longer advertises it as writable, and internal checks draw no rate token on either gate. Includes a residue sweep query for upgrades.

Check validation

  • Every validation finding is now reported (not just the first), with severity, machine code and slug; the extended request projects the org's execution rate before a period change breaks the cap.
  • dash0 shows a check's real custom period instead of snapping it to the ladder, warns before a change would break the org's rate cap, and falls back to the ladder when a stored period is unparseable.

Superadmin entitlements editor

  • Superadmin org-limits endpoints plus a dash0 editor with explicit unlimited toggles; an admin override now outranks the next billing push, an org-admin row surfaces as its own provenance, legacy admin rows are relabeled by migration so they don't resolve as unlimited, and a 403 renders Permission Denied.

Availability

  • Real server-side 24h availability (dash0 renders the real KPI instead of a fabricated 100%), a bucketed check-availability endpoint on the shared engine with region-scoped reads, and an availability strip aligned to the response-time chart in dash0.
  • status0 colors the response-time strip by availability rather than incidents only.

Integrations

  • Gotify and Zulip notification senders, wired into the registry with dash0 forms, icons, locales and docs. A stale organization_providers link no longer permanently bricks the Slack app install — both the Slack and Discord install paths heal it.

Support

  • Per-thread reply pre-flight that refuses unroutable replies before anything is stored, surfaces the reason in the inbox and offers a resend.

Docs

  • The API reference resolves its base URL from the browsing host instead of a hardcoded one, and keeps a deliberate pick consistent across reference pages.

Follow-up fixes

  • Added the missing "Scheduling" breadcrumb leaf (the page previously rendered a bare non-clickable "Checks" crumb).
  • Stopped the design reference page overflowing horizontally on mobile.
  • Corrected a stale E2E expectation that still pointed the rate-limit banner's remedy link at the Usage page instead of the scheduling page.
  • Regenerated the Go client from the final OpenAPI spec at batch end.

Test plan

Each spec landed through the batch gate: backend suite, dash0 unit tests, targeted Playwright E2E, tsc, and lint all green before its chore: all checks passing commit, with an independent completeness audit per spec.

🤖 Generated with Claude Code

fclairamb and others added 30 commits August 26, 2026 23:11
…age, validate warnings, superadmin entitlements

Five specs born from the 2026-08-26 public-org rate-limit starvation
incident (a 1-min check silently skipped for 7.5h while its org sat 2x
over its MaxChecksPerMinute cap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fclairamb and others added 22 commits August 27, 2026 08:03
… threads it cannot possibly answer

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The generated /docs/api pages bake the spec's servers: list in at build
time, so every self-hosted instance advertised https://solidping.io and a
reader copying an example aimed it at a host they do not run.

A swizzled Base URL control (docusaurus-theme-openapi-docs@5.0.2) now
offers the current origin first — except on a known docs host, where
handlerWithDocsHost redirects API paths into the docs site and the spec's
cloud entry is the correct answer. The spec's own servers stay
selectable; only the order and the selection change.
Types for the swizzle: explicit re-exports from the theme slice, a relative
import of the sibling slice, an ambient declaration for @theme/ApiItem/hooks,
and the bun test files excluded from tsconfig.
The theme persists its own automatic default exactly as it persists a
click, so the stored selection alone cannot tell the two apart and the
host default was re-applied on every page mount. A sessionStorage marker
records the URL we selected ourselves; when the restored selection
differs from it, the reader chose it and we leave it alone. The resolved
option list is still offered either way.
docsHostMatches strips the port from the request host but compares the
configured host raw, so SP_DOCS_HOST="docs.acme.com:8443" is never
redirected server-side, while the docs bundle's isKnownDocsHost strips
the port from both and calls it a docs host. Hostname-only is what the
bundle wants; changing the Go side would change which requests get
redirected, so the divergence is named on both functions instead of
silently fixed here.

Also cross-references the build-time SP_DOCS_HOSTS from the runtime
SP_DOCS_HOST config comment, since a prebuilt binary's docs bundle does
not learn a self-hoster's docs host without a rebuild.
… Base URL

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four specs in this batch moved openapi.yaml: availability24h on the check
stats aggregate, the bucketed availability endpoint, the superadmin
entitlements editor, and the support reply pre-flight plus resend.

Verified against the regenerated client: go build, golangci-lint (0 issues)
and the full backend suite all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/orgs/$org/checks/scheduling matched the `isChecks` breadcrumb branch but
had neither a checkUid nor the `new` route id, so it fell through to the
bare list rendering: a single non-clickable "Checks" crumb, identical to
what the checks list page shows, with no leaf naming the page and no way
to click back to the list.

Add an `isScheduling` case that turns the section crumb into a back-link
and appends a "Scheduling" leaf, mirroring how new/edit/result already
work, plus the `nav.scheduling` key in all four locales.

The E2E asserts both halves and was confirmed to fail against a build
without the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every ExampleRow put its preview in a bare grid item, and a grid item
defaults to min-width:auto — so a preview whose intrinsic min-content is
wider than the column widened the track instead of wrapping. At 375px the
over-limit banner example (a w-full max-w-md card, 448px) did exactly
that, pushing the document to 454px and giving the whole page a
horizontal scrollbar.

The guard was already half-present: the md grid used minmax(0,1fr), but
only for the second (code) column, so neither the preview column nor the
single-column mobile layout was covered.

Put both tracks behind minmax(0,…) and give both children min-w-0.
Verified by bisection: hiding #feedback was what dropped the document
back to 375px, and after the fix no section widens it at all.

Fixes the pre-existing failure in
check-http-expected-status-codes.spec.ts:331, which measures whole-page
overflow and so was reporting a #feedback bug from a #token-chips-input
test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The E2E still encoded the pre-2026-08-26-04 link target. That spec
retargeted the banner's remedy link from the Usage page to the check
scheduling page, so the reader is handed the surface that can fix the
overage rather than one restating the number just quoted. The unit tests
moved with it; the E2E did not, and asserted the link was absent on
Usage — where it is now deliberately present and no longer a self-link.

Assert the destination rather than mere presence, so a future retarget
cannot pass silently. Then put the "no self-link" assertion where it is
actually true: the scheduling page, which had no coverage that it
suppresses the link — the invariant the mis-placed assertion was reaching
for.

Diagnosis and proposal from
specs/todos/2026-08-27-05-stale-rate-limit-banner-e2e-expectation.md. Its
step 3 (renaming check-rate-limit-usage-link and org:checkRateLimit.viewUsage,
whose names still say "usage") is deliberately left out: it touches files
with in-flight uncommitted work, and the spec itself says to land steps
1-2 first in that case.

On its step 4 — CI was not masking this behind a skipped E2E job. CI had
simply never run on this branch: the batch accumulated 144 commits with
zero pipeline runs until the PR was opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fclairamb fclairamb changed the title Batch 2026-08-26: check scheduling, availability, entitlements, Gotify/Zulip Batch 2026-08-26-2: rate caps & scheduling, real availability, entitlements editor, Gotify/Zulip, support preflight Aug 27, 2026
@fclairamb
fclairamb merged commit e662950 into main Aug 27, 2026
11 checks passed
@fclairamb
fclairamb deleted the batch/2026-08-26-2 branch August 27, 2026 11:49
fclairamb added a commit that referenced this pull request Aug 27, 2026
The batch landed as one squash commit with a non-conventional subject, so
release-please parsed nothing from it and generated a v0.19.0 section
listing only an unrelated Slack fix. Fill in what actually shipped: 30
features and 21 fixes, grouped by theme and attributed to #269.

Only prose is added — the version header, compare link and every earlier
section are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fclairamb added a commit that referenced this pull request Aug 27, 2026
## Why

This repo squash-merges exclusively, and `squash_merge_commit_title` is
`PR_TITLE`. A pull request title is therefore **not a label — it is the
commit subject that lands on `main`**, and release-please
(`release-type: simple`) parses exactly those subjects to pick the next
version and build the changelog.

Nothing checked that, and the failure is silent: release-please reports
**success** while releasing nothing. The only tell is the release PR's
head SHA never moving.

[#269](#269) hit it. A
147-commit batch titled `Batch 2026-08-26-2: …` produced this on `main`:

```
e662950 Batch 2026-08-26-2: rate caps & scheduling, … (#269)
```

release-please found nothing releasable, so the release PR stayed pinned
at `v0.18.4` — a patch whose changelog covered one unrelated Slack fix —
while ~30 `feat` commits went unrecorded. Recovery needed a manual
`Release-As:` override
([#270](#270)).

## What

A `pull_request` job (`opened`, `edited`, `reopened`, `synchronize`)
that validates the title as `<type>[(scope)][!]: <description>`. Allowed
types track `release-please-config.json`'s `changelog-sections`, so the
two cannot drift apart on what counts as releasable. On failure it
writes an actionable job summary rather than only a red X.

No third-party action — it is a few lines of bash, so there is no
supply-chain surface. The title is passed via `env:` rather than
inlined, since it is attacker-controlled text.

## Verified against the real corpus

Rejects: both of #269's titles, `Update the readme`, `FEAT: …`
(uppercase), `feat add …` (no colon), `feat:no space`.
Accepts: `chore(release): v0.19.0` (release-please's own), `chore(deps):
…` and `fix(deps): … (non-major)` (Renovate), `feat!: …`, and the repo's
existing `fix(slack): …` / `fix(dash0): …` titles.

An earlier draft also enforced a minimum description length. Testing
showed it rejected `chore(release): v0.19.0` and would have broken
**every future release** — dropped before shipping.

## Follow-up (needs a repo admin, not this PR)

The job blocks nothing until it is a **required status check** on
`main`. Once merged:

```
gh api -X PATCH repos/fclairamb/solidping/branches/main/protection/required_status_checks \
  -f 'checks[][context]=Conventional commit title'
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant