Feature 05: Checks & statuses — internal/checks #11

Closed
opened 2026-09-04 04:25:13 +00:00 by crueber · 4 comments
Owner

Feature 05: Checks & statuses — internal/checks

Spec: docs/features/05_checks_statuses.md (normative) + P1/P4–P8 in docs/features/README.md.
Rollout: Wave B class in docs/features/09_rollout.md §3 (parallel with issues/notify core trays). Depends on 01 (roles), 03 (merge task, open-PR index).

  • New package internal/checks: one api.RouteProvider (Seam 1, both lanes via api.Lanes), one auth.Provider claiming the wct_ prefix (Seam 2 — startup-validated no overlap), zero new policy effects (§6 extends the existing protect effect with require_checks), one task kind checks-index-compact (Seam 5). v1 is statuses-only; check-runs/suites deferred (§1 seam note, no migration needed later).
  • Objects (repos/<o>/<r>/checks/): checks/<sha>/<context>.json (Create first report, CAS Update after; last-write-wins; never deleted), checks/index.json (P4 CAS'd hot-window projection, newest 500 shas, 256 KiB cap), meta/ci_tokens/<id>.json (CAS'd; only sha256 hash stored; revoked records retained).
  • CI tokens: secret wct_<id>.<secret> shown once; provider resolves unprivileged ci:<id> principal (frozen Principal NOT extended); handler checks checks:write scope + revoked_at (mismatch/revoked = 401, valid-but-scopeless = 403). Repo write role may also report.
  • Report flow (§2 steps 1–4): validate context/state/sha (sha must resolve via RepoView.Commit, else 404) → Create-or-CAS status → CAS index upsert → SSE check packet + failure/error notification enqueue, all synchronously post-CAS (P8). Bounded CAS retries (≤5, then 503).
  • Combined view (§5): worst-of error > failure > pending > success; zero contexts ⇒ pending with zero counts. GETs are no-store (sha-addressed but mutable — never immutable class).
  • Required checks (§6): require_checks: [1–32 validated contexts] inside protect; union across matching rules; evaluated by 03's merge task at merge time only (wire into the existing merge gate next to the 04 required-reviews check; refusal message verbatim merge refused: required checks not green for <sha>: …). No bypass list. Direct pushes NOT gated.
  • Notifications (§8): on transition into failure/error where sha is an open PR head (bounded lookup via shared issues/index.json), enqueue 06's check_reported synchronously. Success/pending emit nothing.
  • UI/SDK (§9): checks table page, check pills on commits + PR head, merge-button tooltip from combined view + policy, CI-tokens admin settings, web/sdk/src/checks.js. Dark + light themes.

Acceptance criteria

  • Endpoints in 05 §4 with P6 gates; plain-text errors; paginated index (n default 50, max 200).
  • Merge gate refuses non-green required contexts with the verbatim message; e2e proven (post status → merge blocked → post success → merge lands).
  • make cover ≥ 95% on internal/checks; -race clean; table-driven httptest per handler.
  • EVIDENCE.md entry: report-path round trips, combined-view read fan-out bounds, why it can't explode.
# Feature 05: Checks & statuses — `internal/checks` **Spec:** `docs/features/05_checks_statuses.md` (normative) + P1/P4–P8 in `docs/features/README.md`. **Rollout:** Wave B class in `docs/features/09_rollout.md` §3 (parallel with issues/notify core trays). Depends on 01 (roles), 03 (merge task, open-PR index). ## Recommended implementation (verified against the doc) - **New package `internal/checks`**: one `api.RouteProvider` (Seam 1, both lanes via `api.Lanes`), one `auth.Provider` claiming the `wct_` prefix (Seam 2 — startup-validated no overlap), zero new policy effects (§6 extends the existing `protect` effect with `require_checks`), one task kind `checks-index-compact` (Seam 5). v1 is **statuses-only**; check-runs/suites deferred (§1 seam note, no migration needed later). - **Objects** (`repos/<o>/<r>/checks/`): `checks/<sha>/<context>.json` (Create first report, CAS Update after; last-write-wins; never deleted), `checks/index.json` (P4 CAS'd hot-window projection, newest 500 shas, 256 KiB cap), `meta/ci_tokens/<id>.json` (CAS'd; only sha256 hash stored; revoked records retained). - **CI tokens:** secret `wct_<id>.<secret>` shown once; provider resolves unprivileged `ci:<id>` principal (frozen `Principal` NOT extended); handler checks `checks:write` scope + `revoked_at` (mismatch/revoked = 401, valid-but-scopeless = 403). Repo `write` role may also report. - **Report flow (§2 steps 1–4):** validate context/state/sha (sha must resolve via `RepoView.Commit`, else 404) → Create-or-CAS status → CAS index upsert → SSE `check` packet + failure/error notification enqueue, all synchronously post-CAS (P8). Bounded CAS retries (≤5, then 503). - **Combined view (§5):** worst-of `error > failure > pending > success`; zero contexts ⇒ `pending` with zero counts. GETs are **no-store** (sha-addressed but mutable — never immutable class). - **Required checks (§6):** `require_checks: [1–32 validated contexts]` inside `protect`; union across matching rules; evaluated by **03's merge task at merge time only** (wire into the existing merge gate next to the 04 required-reviews check; refusal message verbatim `merge refused: required checks not green for <sha>: …`). No bypass list. Direct pushes NOT gated. - **Notifications (§8):** on transition into `failure`/`error` where sha is an open PR head (bounded lookup via shared `issues/index.json`), enqueue 06's `check_reported` synchronously. Success/pending emit nothing. - **UI/SDK (§9):** checks table page, check pills on commits + PR head, merge-button tooltip from combined view + policy, CI-tokens admin settings, `web/sdk/src/checks.js`. Dark + light themes. ## Acceptance criteria - [ ] Endpoints in 05 §4 with P6 gates; plain-text errors; paginated index (`n` default 50, max 200). - [ ] Merge gate refuses non-green required contexts with the verbatim message; e2e proven (post status → merge blocked → post success → merge lands). - [ ] `make cover` ≥ 95% on `internal/checks`; `-race` clean; table-driven httptest per handler. - [ ] EVIDENCE.md entry: report-path round trips, combined-view read fan-out bounds, why it can't explode.
Author
Owner

Starting Feature 05 (checks & statuses) implementation on branch feat/checks (transplant from origin/main to protect the dirty worktree). Plan: new internal/checks (statuses, index+compact, wct_ tokens, combined view, require_checks gate via pulls ChecksGate seam), policy require_checks extension, server TokenHook seam, SDK + UI pages, EVIDENCE E6. Will link the PR here when ready.

Starting Feature 05 (checks & statuses) implementation on branch feat/checks (transplant from origin/main to protect the dirty worktree). Plan: new internal/checks (statuses, index+compact, wct_ tokens, combined view, require_checks gate via pulls ChecksGate seam), policy require_checks extension, server TokenHook seam, SDK + UI pages, EVIDENCE E6. Will link the PR here when ready.
Author
Owner

Feature 05 implementation complete: PR crueber/walhub#16 (feat/checks, one commit on top of origin/main). Done per the issue plan: internal/checks (statuses, index+compact, wct_ tokens + Seam 2 hook, combined view, merge-time gate via pulls ChecksGate seam without forking merge logic), require_checks in protect (union, push-ignored), nil-safe 06-shaped fan-out, UI pages + web/sdk/src/checks.js (dark+light via existing dark: variants). Acceptance: endpoints+P6 gates with plain-text errors and paged index; verbatim refusal proven e2e (unit pulls e2e test + live stack: failure, merge refused verbatim, success, merge lands); internal/checks 96.4% cover with -race clean and table-driven httptest per handler; EVIDENCE.md E6 with measured budgets. Deviations noted in 05 Decisions + 14 Decisions: discovery absent (01/02/03/C2 rule), open-PR lookup via bounded pr.json scan (cards lack head_sha), bypass applies per-rule (03 step 4), 409 on bad state, index-exhaustion answers 200 (02 parity), live SSE rows deferred to the 06/08 stream endpoint. Gaps: make cover internal/git pre-existing float-boundary failure (untouched files); no real-browser drive available here (vite build + live shell serving verified instead). Do NOT merge — awaiting review.

Feature 05 implementation complete: PR crueber/walhub#16 (feat/checks, one commit on top of origin/main). Done per the issue plan: internal/checks (statuses, index+compact, wct_ tokens + Seam 2 hook, combined view, merge-time gate via pulls ChecksGate seam without forking merge logic), require_checks in protect (union, push-ignored), nil-safe 06-shaped fan-out, UI pages + web/sdk/src/checks.js (dark+light via existing dark: variants). Acceptance: endpoints+P6 gates with plain-text errors and paged index; verbatim refusal proven e2e (unit pulls e2e test + live stack: failure, merge refused verbatim, success, merge lands); internal/checks 96.4% cover with -race clean and table-driven httptest per handler; EVIDENCE.md E6 with measured budgets. Deviations noted in 05 Decisions + 14 Decisions: discovery absent (01/02/03/C2 rule), open-PR lookup via bounded pr.json scan (cards lack head_sha), bypass applies per-rule (03 step 4), 409 on bad state, index-exhaustion answers 200 (02 parity), live SSE rows deferred to the 06/08 stream endpoint. Gaps: make cover internal/git pre-existing float-boundary failure (untouched files); no real-browser drive available here (vite build + live shell serving verified instead). Do NOT merge — awaiting review.
Author
Owner

PR #16 review (feat/checks, commit d26f196) — verified in scratch worktree /tmp/walhub-checks, main worktree untouched, nothing committed.

VERDICTS

  • Import budget: PASS. internal/checks direct imports are stdlib only + existing internal pkgs (git/identity/policy/server/auth/store; git import is the ParseRepoId pattern shared with issues/review http.go). No new third-party modules.
  • Seam direction: PASS. Nothing under internal/{store,wal,git,server,api} imports internal/checks.
  • CAS discipline: PASS. Report is Create-then-CAS (service.go:215-254, 412 falls into bounded casUpdate <=5 then 503); index CAS <=5 attempts then proceeds without the index (best-effard projection, recorded deviation); token id alloc retries <=5 on 412; gate has 15s GateTimeout, fails closed.
  • Token security: PASS. Only token_hash stored (model.go:267-280 sha256+constant-time compare); secret in TokenCreated once (service.go:725), TokenView carries no secret; revoked records retained, revoke idempotent; zero log calls in non-test checks code (nothing to leak). 401 mismatch/revoked, 403 scopeless — matches 05 S3.
  • Frozen Principal: PASS. Zero diff in internal/server/auth/ and internal/identity/; wct_ resolves to unprivileged ci: via AuthService.ExtraCredential hook (server/auth.go extra()), capability checked handler-side.
  • require_checks merge-only: PASS. Single call site pulls/merge.go:159 (runMerge step 4, next to the 04 gate); ProtectEffect.Evaluate ignores RequireChecks; no RequireChecks refs in internal/git or internal/server push paths. Union + verbatim refusal message confirmed in service.go:926-969. Nil backend fails closed only when a rule carries the gate.
  • Combined/status GETs no-store: PASS (http.go writeJSON always sets Cache-Control: no-store). Wire: []-never-null holds on all views; invalid state -> 409 ErrInvalidState, unknown sha -> 404, bad sha -> 400.
  • P6 gates: PASS (admin-only token CRUD, write-or-CI report, read-gated GETs via Roles.CheckRead).
  • Cross-shape coupling: PASS. openPRHeads parses issues Card{num,kind,state} + pr.json Endpoint{sha}/merged + thread header — all match owner shapes; bounded at 200 rows, best-effort per P8.
  • UI/SDK: PASS. /:owner/:repo/checks route + Repo tab, dark: variants present (Checks.jsx), pills in Commit/Commits/Pull, admin token panel in Settings; web/sdk/src/checks.js + node tests 3/3 pass.
  • EVIDENCE E6: PASS and plausible — harness internal/checks/evidence_test.go passes; numbers match the code (report 2 PUTs + re-read fan-out linear in k, combined 1 LIST + k GETs, gate under deadline, index best-effort).
  • Deviations recorded: PASS. 05 implementation notes + 03 note + 14 Wave-05 amendment in the same commit (no-discovery, ExtraCredential-vs-Provider, pr.json-not-cards, index-exhaustion-200, refetch-not-SSE).

TESTS (scratch worktree, -race)

  • gofmt: clean; go vet ./internal/checks/...: clean; go build ./...: clean.
  • go test -race ./internal/checks/... : ok, coverage 96.4% statements (>=95 gate).
  • go test -race ./internal/pulls/... ./internal/policy/... : ok (gate wiring + e2e).
  • go test -race ./internal/server/ : ok (auth hook).
  • node --test web/test/unit/sdk-checks.test.js : 3/3 pass.
  • go test -run TestEvidence ./internal/checks/ : ok.

No fixes needed — I pushed nothing. Findings: none blocking; two recorded-and-accepted doc deltas (discovery Name() absent like all sibling waves; SSE packet implemented but stream unwired until 06/08 lands).

MERGE RECOMMENDATION: ready to merge.

PR #16 review (feat/checks, commit d26f196) — verified in scratch worktree /tmp/walhub-checks, main worktree untouched, nothing committed. VERDICTS - Import budget: PASS. internal/checks direct imports are stdlib only + existing internal pkgs (git/identity/policy/server/auth/store; git import is the ParseRepoId pattern shared with issues/review http.go). No new third-party modules. - Seam direction: PASS. Nothing under internal/{store,wal,git,server,api} imports internal/checks. - CAS discipline: PASS. Report is Create-then-CAS (service.go:215-254, 412 falls into bounded casUpdate <=5 then 503); index CAS <=5 attempts then proceeds without the index (best-effard projection, recorded deviation); token id alloc retries <=5 on 412; gate has 15s GateTimeout, fails closed. - Token security: PASS. Only token_hash stored (model.go:267-280 sha256+constant-time compare); secret in TokenCreated once (service.go:725), TokenView carries no secret; revoked records retained, revoke idempotent; zero log calls in non-test checks code (nothing to leak). 401 mismatch/revoked, 403 scopeless — matches 05 S3. - Frozen Principal: PASS. Zero diff in internal/server/auth/ and internal/identity/; wct_ resolves to unprivileged ci:<id> via AuthService.ExtraCredential hook (server/auth.go extra()), capability checked handler-side. - require_checks merge-only: PASS. Single call site pulls/merge.go:159 (runMerge step 4, next to the 04 gate); ProtectEffect.Evaluate ignores RequireChecks; no RequireChecks refs in internal/git or internal/server push paths. Union + verbatim refusal message confirmed in service.go:926-969. Nil backend fails closed only when a rule carries the gate. - Combined/status GETs no-store: PASS (http.go writeJSON always sets Cache-Control: no-store). Wire: []-never-null holds on all views; invalid state -> 409 ErrInvalidState, unknown sha -> 404, bad sha -> 400. - P6 gates: PASS (admin-only token CRUD, write-or-CI report, read-gated GETs via Roles.CheckRead). - Cross-shape coupling: PASS. openPRHeads parses issues Card{num,kind,state} + pr.json Endpoint{sha}/merged + thread header — all match owner shapes; bounded at 200 rows, best-effort per P8. - UI/SDK: PASS. /:owner/:repo/checks route + Repo tab, dark: variants present (Checks.jsx), pills in Commit/Commits/Pull, admin token panel in Settings; web/sdk/src/checks.js + node tests 3/3 pass. - EVIDENCE E6: PASS and plausible — harness internal/checks/evidence_test.go passes; numbers match the code (report 2 PUTs + re-read fan-out linear in k, combined 1 LIST + k GETs, gate under deadline, index best-effort). - Deviations recorded: PASS. 05 implementation notes + 03 note + 14 Wave-05 amendment in the same commit (no-discovery, ExtraCredential-vs-Provider, pr.json-not-cards, index-exhaustion-200, refetch-not-SSE). TESTS (scratch worktree, -race) - gofmt: clean; go vet ./internal/checks/...: clean; go build ./...: clean. - go test -race ./internal/checks/... : ok, coverage 96.4% statements (>=95 gate). - go test -race ./internal/pulls/... ./internal/policy/... : ok (gate wiring + e2e). - go test -race ./internal/server/ : ok (auth hook). - node --test web/test/unit/sdk-checks.test.js : 3/3 pass. - go test -run TestEvidence ./internal/checks/ : ok. No fixes needed — I pushed nothing. Findings: none blocking; two recorded-and-accepted doc deltas (discovery Name() absent like all sibling waves; SSE packet implemented but stream unwired until 06/08 lands). MERGE RECOMMENDATION: ready to merge.
Author
Owner

Feature 05 complete: PR #16 reviewed with no blocking defects, merged as 64e79c7. internal/checks 96.4% coverage, -race clean. Closing.

Feature 05 complete: PR #16 reviewed with no blocking defects, merged as 64e79c7. internal/checks 96.4% coverage, -race clean. Closing.
crueber added this to the v1 milestone 2026-09-10 22:20:53 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#11
No description provided.