Skip to content

batch/2026-06-20: status model, SSL/Docker checks, MCP OAuth, config-as-code (8 specs) - #85

Merged
fclairamb merged 75 commits into
mainfrom
batch/2026-06-20
Jun 20, 2026
Merged

fclairamb merged 75 commits into
mainfrom
batch/2026-06-20

Conversation

@fclairamb

Copy link
Copy Markdown
Owner

Integration branch bundling 8 specs implemented this session (each on its own feat/ branch, audited for completeness, then --no-ff merged here). 74 commits ahead of main.

What's included

Frontend / brand (low-risk):

  • Check-detail header — action toolbar drops onto its own row so a long check name no longer squeezes it; design-reference variant + E2E.
  • Destructive button — red-tinted elevation tokens so Delete matches the Save button's hover lift; E2E.
  • Favicon regen — the 4 favicon/touch PNGs re-rendered from the new logo.svg (verified byte-match) and propagated to both public/ + embedded dirs.

Status model (foundational):

  • First-class Warning (live, value 8) + Degraded (aggregated rollup, value 7) across all three status enums; widened results.status CHECK (both dialects, also fixing the missing 6/error); Warning counts as up for availability; aggregation promotes a non-failing window containing a Warning to Degraded with a Severity() tie-break; Warning is incident-neutral (never opens/resolves an incident); one shared statusStyle() util across dash0 + status0.

Checks (build on the status model):

  • SSL graduated expiry — two-tier warningDays/criticalDays (legacy thresholdDays aliased), expiry decided by the whole-chain minimum, chain-report output + UI. Warning tier → amber (no page); critical → Down (pages).
  • Docker restart-loop detection — opt-in heuristic (restartLoopMinRestarts/restartLoopWindow); a running-but-crash-looping container → Warning (doesn't page); not-running/unhealthy still → Down.

Larger features:

  • MCP OAuth 2.1 — turns SolidPing into an embedded OAuth 2.1 AS for the MCP endpoint: discovery (/.well-known/*) + RFC 8707 audience binding, /authorize+consent+/token with mandatory PKCE S256, single-use codes, rotating refresh, RFC 7591 DCR with loopback exception. PAT-bearer back-compat preserved.
  • Config-as-code — POST /orgs/:org/checks/apply (+ sp apply) reconciling the existing export format: managed-label scope, dry-run/diff, delete-by-absence gated by prune + managed-label + deletion cap, ${env:}/${param:} secret references resolved server-side. sp checks export/import added.

Reviewer notes / caveats

  • MCP OAuth needs a human security review before it ships. The spec flags it explicitly; the automated audit confirms code-consistency, not a security sign-off. v1 detail: jwks_uri serves an empty keyset because tokens are signed with a symmetric HS256 key (publishing it would leak the secret); an asymmetric keyset is the documented follow-on. Four design forks (SaaS multi-tenant issuers, asymmetric JWKS, first real-client validation, per-org issuer) were left to revisit during that review.
  • Behavior change: config-as-code retro-gated the existing /checks/export and /checks/import endpoints to admin-only (they were auth-only). A non-admin who scripted them will now get 403 — worth a release note.
  • Deferred by design: config-as-code Phase 3 (Docker-label discovery), SSL/Docker Warning notification fan-out, and wiring the graded-expiry helper into checkdomain.
  • Migrations: status model widens the results.status CHECK in 001 (both dialects); MCP OAuth adds 002_mcp_oauth (both dialects); config-as-code adds no migration.
  • Test env: on macOS, embedded-postgres intermittently fails to start (dyld code-signing) — pre-existing and unrelated; the suites that matter ran green via testcontainers/SQLite.

🤖 Generated with Claude Code

fclairamb and others added 30 commits June 20, 2026 02:01
Specs for the genuine feature gaps surfaced by docs/competitors/maintenant.md,
grounded in the current code:

- todos/2026-06-20-01 SSL graduated expiry (warning/critical tiers) +
  whole-chain nearest-expiry & chain reporting
- todos/2026-06-20-02 Docker restart-loop (crash-loop) detection
- todos/2026-06-20-03 OAuth 2.1 authorization for the MCP endpoint
- todos/2026-06-20-04 first-class StatusWarning (degraded-but-up) status —
  enabler that 01 and 02 depend on
- ideas/2026-06-20-config-as-code declarative checks + Docker-label discovery

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code-verified every claim in 2026-06-20-04 against the tree and folded in
corrections:

- checkerdef.Status has no Created (starts Running=2)
- new finding: aggregation rollup tie-break (calculateDominantStatus) ranks by
  raw status number, so Warning=7 would mask outages — add Severity() helper +
  severity-aware tie-break (Decision D)
- frontend wire string is split between "warning" and "degraded"; standardise on
  "degraded" + one shared statusStyle() util (Decision C); per-file table, plus
  two previously-missed components (status-timeline, check-summary-cards)
- incident classifier early-returns before the visible-status update; warning
  needs a third path modelled on the display-only CheckStatusValidating
- slack statusIntToString currently maps warning to "unknown"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odel

Spec 04 reshaped per user answers to the open questions:

- two statuses: Warning (live, value 8, "up but something to report") +
  Degraded (aggregated, value 7); aggregation promotes raw Warning -> rolled-up
  Degraded
- availability: warning counts as up; incidents: warning neutral
- constraint: amend 001 in place, widen results.status to (0..8) incl. the
  previously-missing 6 (error)
- frontend: one shared statusStyle() util mapping both "warning" and "degraded"
- record follow-on sub-decisions (values 7/8, rollup promotion rule, role of
  CheckStatusDegraded) and the five status->string sites found in review

Specs 01 (SSL) and 02 (restart-loop) realigned: a live warning shows current
status Warning; Degraded appears only in rolled-up history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve open questions in the 2026-06-20 todos and align ideas with the
new Warning/Degraded status model.

- ssl (-01): warningDays defaults to criticalDays (30); criticalDays stays
  30 (docs recommend 7); checkdomain tiering is a follow-up.
- docker (-02): Approach A heuristic MVP; loop => StatusWarning;
  minRestarts=3/window=120s; firm dependency on -04.
- mcp-oauth (-03): embedded AS + DCR in scope (Phase 3); 4 unrecommended
  forks kept as explicit open questions with leanings.
- ideas: flag stale enum/warn_days in check-interface as superseded; note
  Warning is incident-neutral in notification-channels & telegram specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fclairamb and others added 22 commits June 20, 2026 03:57
Embedded OAuth 2.1 AS for the MCP resource: discovery docs (RFC 9728/8414),
authorize/token/register endpoints, PKCE S256, audience binding (RFC 8707),
single-use codes, rotating refresh tokens, DCR (RFC 7591) with loopback
redirect exception, and revocation on logout/PAT-revoke. Reuses the existing
HS256 JWT signing key and user session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dash0 route /orgs/$org/oauth/consent renders the requested scope (read-only
vs read-write) and posts approve/deny back to /api/v1/oauth/authorize via a
native form so the access_token cookie rides along. Server handles deny with
access_denied. Built from design-reference primitives (Card/Button/Alert);
auth enforced by the parent org layout (401 -> login?returnTo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…res)

Table-driven tests (testify/require, t.Parallel): well-formed discovery docs;
unauthenticated MCP request -> 401 with WWW-Authenticate resource_metadata
pointer; missing/plain PKCE rejected; reused/expired auth code rejected;
wrong/blocked redirect_uri rejected and loopback accepted; wrong aud rejected
at the MCP gate; refresh rotation invalidates the prior token; mcp:read scope
flows to claims; DCR public-vs-confidential. Cross-engine OAuth-repo parity
(testOAuthRepos) runs under both SQLite and embedded Postgres.

Also: lint fixes (RFC snake_case nolint, sentinel error renames, var naming,
hugeParam pointers) and mock db.Service stubs for the new OAuth methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframe the config-as-code idea around what SolidPing already ships
(export schema, idempotent upsert-by-slug import, sp CLI, secret
envelope + parameters table, check labels). The gap narrows to the
reconcile loop: delete-by-absence within a managed-label scope,
dry-run/diff, secret references (${env}/${param}), an sp apply verb,
and admin-gating. Resolves the five open questions against the code and
phases the work; demotes Docker-label discovery to an optional suggest
source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move specs/ideas/2026-06-20-config-as-code.md to
specs/todos/2026-06-20-05-config-as-code.md (NN=05, next free for the
day). Update the banner from "idea" to active spec and fix the sibling
links to the ideas/ folder that the move would otherwise break.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…in gating

- Reconcile/plan engine (create/update/delete/unmanaged/rename) scoped by the
  reserved solidping.io/managed=<manifest> label
- ApplyChecks service: dryRun (plan only), prune+managed-label+deletion-cap
  delete-by-absence, never touches unmanaged checks
- Secret references ${env:NAME}/${param:KEY} resolved server-side; missing ref
  is a hard error; plaintext-fallback emits a warning
- POST /checks/apply endpoint accepting JSON and YAML bodies
- Admin-gate apply, and retro-gate /checks/export + /checks/import to admin-only
- sp apply [-f] <manifest> [--dry-run] [--prune] [--force] [--yes]: computes a
  dry-run plan first, prints it, prompts before mutating unless --yes
- sp checks export [--file] and sp checks import <file> [--dry-run] wrappers
- client methods ApplyChecks/ExportChecks/ImportChecks (raw HTTP; apply sends
  the manifest bytes with a YAML/JSON content-type)
Adds table-driven coverage for the apply reconcile engine and resolves
pre-existing lint findings in the apply/CLI/client code from the prior
implementation commits.

Tests:
- plan computation (create/update/delete/unmanaged), dryRun mutates nothing
- delete-by-absence only with prune+managed-label+within-cap; cap refuses
  oversized prune (force overrides); unmanaged checks never touched
- secret-ref resolution (${env}/${param}, missing -> error, plaintext
  fallback -> warning); rename via previousSlug
- JSON and YAML bodies parse to the same plan (internal + HTTP)
- export->apply round-trip idempotence (apply of an export is no-op/update)
- admin-only gating on apply AND export/import (non-admin -> 403)

Lint: queryTrue/colSlug constants, checked json marshals, dropped unused
nolint directives and the unused rawRequestBytes status return.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new "header stacks the action toolbar" test asserted no horizontal
overflow on the whole document (documentElement.scrollWidth), which is
tripped by unrelated page content (a chart that sizes differently in
headless CI) rather than the header it is testing — failing all 3 CI
attempts though it passes locally. Add a data-testid to the header
container and scope the overflow check to that element. The header
(flex-col title row + flex-wrap toolbar + truncating h1) cannot overflow
its own width, and this still catches a real header regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fclairamb
fclairamb merged commit 936d830 into main Jun 20, 2026
10 checks passed
@fclairamb
fclairamb deleted the batch/2026-06-20 branch June 20, 2026 22:44
@fclairamb fclairamb mentioned this pull request Jun 19, 2026
fclairamb added a commit that referenced this pull request Jun 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](v0.0.0...v0.1.0)
(2026-06-22)


### Features

* add check type registry, sample configs, notification senders, and
observability integrations
([eea049e](eea049e))
* batch — Warning/Degraded statuses, SSL/Docker check upgrades, MCP
OAuth 2.1, config-as-code
([#85](#85))
([936d830](936d830))
* batch spec implementations (dashboard UI, jobs, notifications, SaaS,
perf) ([#78](#78))
([fe003e4](fe003e4))
* bulk feature integration (discovery, web push, badges, webhooks, and
more) ([#73](#73))
([a976856](a976856))
* custom user-agent, refresh checks, vite plugin switch, CI updates
([#1](#1))
([cc00858](cc00858))
* documentation site served at /docs (co-located in web/docs)
([#88](#88))
([27d9ccd](27d9ccd))
* email check frontend (spec 03)
([#30](#30))
([aebd106](aebd106))
* email inbox foundation via JMAP (spec 01)
([#24](#24))
([0f3b0c1](0f3b0c1))
* email passive checks (spec 02)
([#27](#27))
([ba5536f](ba5536f))
* **entitlements:** trim to MaxSSOUsers + MaxChecksPerMinute + status
page history fixes
([#50](#50))
([a007de0](a007de0))
* **entitlements:** trim to MaxSSOUsers + MaxChecksPerMinute, enforce
both ([#49](#49))
([d25cc5f](d25cc5f))
* group incident correlation (spec 04 backend v1)
([#31](#31))
([1883986](1883986))
* **observability:** instrument hot path + add bench harness
([#60](#60))
([c6604cc](c6604cc))
* **oncall:** searchable timezone dropdown + sidebar logo polish
([#46](#46))
([4396898](4396898))
* per-IP HTTP rate limiting and concurrency limiting
([#59](#59))
([f9cc273](f9cc273))
* soften HTTP rate limits with bounded queues + request timeout
([#63](#63))
([025501b](025501b))
* SolidPing — distributed uptime monitoring platform
([eef4383](eef4383))
* **statuspages:** drag-and-drop resource reordering + dash conventions
([#41](#41))
([cf910d4](cf910d4))


### Bug Fixes

* **deps:** update dependency i18next to v26
([#26](#26))
([d5d1b7e](d5d1b7e))
* **deps:** update dependency lucide-react to v1
([#28](#28))
([cdc8e8c](cdc8e8c))
* **deps:** update dependency react-i18next to v17
([#29](#29))
([13135d8](13135d8))
* **deps:** update dependency recharts to v3.8.1
([#14](#14))
([5d95a3b](5d95a3b))
* **deps:** update go dependencies (non-major)
([#16](#16))
([2450f13](2450f13))
* **deps:** update go dependencies (non-major)
([#19](#19))
([f842310](f842310))
* **deps:** update go dependencies (non-major)
([#37](#37))
([c020eca](c020eca))
* **deps:** update go dependencies (non-major)
([#44](#44))
([ff0bcd0](ff0bcd0))
* **deps:** update go dependencies (non-major)
([#52](#52))
([2e3bc28](2e3bc28))
* **deps:** update go dependencies (non-major)
([#69](#69))
([a65ed4c](a65ed4c))
* **deps:** update go dependencies (non-major)
([#77](#77))
([a0920ff](a0920ff))
* **deps:** update go dependencies (non-major) to v1.4.2
([#86](#86))
([1eb7504](1eb7504))
* **deps:** update module github.com/aws/aws-sdk-go-v2/config to
v1.32.18 ([#72](#72))
([6406537](6406537))
* **deps:** update module github.com/aws/aws-sdk-go-v2/service/s3 to
v1.101.0 ([#39](#39))
([f9c9563](f9c9563))
* **deps:** update module github.com/go-webauthn/webauthn to v0.17.3
([#45](#45))
([593931a](593931a))
* **deps:** update module github.com/go-webauthn/webauthn to v0.17.4
([#70](#70))
([fca1c97](fca1c97))
* **deps:** update module github.com/ibm/sarama to v1.48.1
([#47](#47))
([93ccf3a](93ccf3a))
* **deps:** update module github.com/ibm/sarama to v1.48.2
([#54](#54))
([e84d868](e84d868))
* **deps:** update module github.com/ibm/sarama to v1.49.0
([#64](#64))
([fd61bd7](fd61bd7))
* **deps:** update module github.com/oapi-codegen/runtime to v1.4.1
([#65](#65))
([a3a3e9f](a3a3e9f))
* **deps:** update module github.com/prometheus/common to v0.67.5
([#61](#61))
([095dd6f](095dd6f))
* **deps:** update module github.com/slack-go/slack to v0.24.0
([#75](#75))
([6e86d5b](6e86d5b))
* **deps:** update module golang.org/x/sys to v0.44.0
([#42](#42))
([a245730](a245730))
* **deps:** update module golang.org/x/sys to v0.45.0
([#67](#67))
([49c3d90](49c3d90))
* **deps:** update module golang.org/x/term to v0.43.0
([#43](#43))
([4a934f1](4a934f1))
* **deps:** update module google.golang.org/grpc to v1.81.0
([#36](#36))
([1be183a](1be183a))
* **deps:** update module google.golang.org/grpc to v1.81.1
([#55](#55))
([6bfac2d](6bfac2d))
* status lifecycle improvements and created/running result handling
([#5](#5))
([fc64c7d](fc64c7d))
* **status0:** white page header and de-duplicated footer
([#48](#48))
([77506a7](77506a7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
fclairamb added a commit that referenced this pull request Jul 5, 2026
#105)

## Summary

release-please's changelog only sees each squash-merge commit's own
title/body. The v0.1.0 and v0.2.0 batch integration PRs (#73, #78, #85,
#89) each bundled many independently-implemented, independently-audited
specs behind one flattened changelog bullet, hiding most of what
actually shipped.

Expands both releases' changelog entries to list every underlying
feature/fix, sourced from each batch PR's own itemized description
(which already documented them in detail — the changelog just never
surfaced it). No functional/behavioral change, `CHANGELOG.md` only, plus
one unrelated one-line `.gitignore` fix for a local E2E test-server pid
file that kept showing up as untracked.

## Test plan

- [x] Docs-only change, no code touched.
- [x] Cross-checked every expanded bullet against its source PR's own
description (`gh pr view <n>`) rather than reconstructing from raw
commit diffs.

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Sep 8, 2026
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