feat(jobs): AUTH-004 synthetic prober for the prod login loop - #68
Merged
Merged
Conversation
5 tasks
Every 5 minutes the worker drives the full browser-shaped login loop
against prod (POST /auth/email/start + OPTIONS/POST /auth/exchange
CORS contract + GET /auth/me bearer) and pages on regression. Closes
the gap that hid prod login broken for ~24h on 2026-05-29 (three
stacked failures: client never POSTed exchange, Accept header forced
preflight rejected, api response missing Access-Control-Allow-Credentials).
The exchange-headers leg asserts ACAO ∈ {instanode.dev, www.instanode.dev}
AND ACAC=true on both preflight and real POST — the exact surface the
chain broke. Cookie-mint path intentionally NOT taken (would expand
JWT-secret blast radius to the worker pod for marginal end-to-end gain
that leg 3 already covers via a probe-only service-account bearer).
Metrics: instant_auth_probe_outcome_total{leg,result} +
instant_auth_probe_latency_seconds{leg}. Audit row + structured
ERROR log on fail. NR alert + Prom rule + dashboard tile + catalog
row land in infra repo follow-up PR.
Coverage block:
Symptom: AUTH-004 chain (3 stacked failures hid prod login ~24h)
Enumeration: rg -F 'Access-Control-Allow-Credentials' under api/
+ rg 'exchangeCookieName' under api/internal/handlers/
Sites found: 1 CORS emit site (api router), 1 cookie name const,
3 setExchangeCookie call sites (auth.go x2, magic_link.go)
Sites touched: 0 in api/instanode-web (those fixes already shipped:
api PR #198, web PRs #150 #151) — this PR adds the
detection layer that should have caught all three.
Coverage test: TestAuthProbe_ExchangeHeadersMissing_FailsLeg2 fails
immediately if api ever stops emitting ACAC. Sibling
tests cover preflight 403 + wildcard origin + 5xx
email_start + 401 me + DNS fail + bearer-unset.
Live verified: pending first deploy — prod /metrics scrape for
instant_auth_probe_outcome_total{result="pass"} > 0
in the PR description after auto-deploy lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mastermanas805
force-pushed
the
feat/auth-004-synthetic-prober
branch
from
May 30, 2026 11:44
76dfffe to
4494b48
Compare
CI's diff-cover gate (100% of changed lines per
feedback_coverage_95_floor_100_patch.md) flagged 64 missing lines on
the initial PR. Adds:
- TestAuthProbe_EmailStart_BodyParseErr — JSON parse-err branch
- TestAuthProbe_AllLegsDegraded_OnSlowResponses — latency budget
- TestAuthProbe_AuditInsertFails_DoesNotCrash — DB err path
- TestAuthProbe_PostHandshakeCORSMissing_FailsLeg2 — post path
- TestAuthProbe_PostHTTPError_FailsLeg2 — POST network err
- TestAuthProbe_Me_BodyParseErr + _MissingEmailField
- TestAuthProbe_NilHTTPClient_GetsDefault — exercises 302 redirect
closure via httptest 302 response
- TestAuthProbe_NilMetrics_NoCrash — nil-metrics safety
- TestValidateAuthProbeBaseURL_ParseError + missing-host (sub-cases)
- TestAuthProbe_BadBaseURL_HitsBuildRequestErr — \x7f in URL hits
every leg's build_request err branch
- TestAuthProbeArgs_Kind — trivial method coverage
- TestTruncateForLog_Truncated — full round-trip
- TestAuthProbe_TruncateForLog_DirectCall (internal_test.go) — direct
helper call so diff-cover attributes the truncated branch
- TestAuthProbeConfig_Defaults_AllEmpty — empty-cfg → all defaults
Two unreachable defensive branches removed:
- json.Marshal on a map[string]string (no MarshalJSON, cannot fail)
- http.NewRequestWithContext on a URL the preflight already validated
Per-function coverage on internal/jobs/auth_probe.go is now 100%
across the board. Full package coverage 96.9% (above 95% floor).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lint flagged the `switch { case r.URL.Path == ...:` form in
slowHandler. Convert to tagged-switch on r.URL.Path. happyHandler
keeps the boolean-switch form because it composes Path AND Method
conditions per arm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
AuthProbeWorker) that drives the full browser-shaped login loop against prod and pages on regression — closes the gap that hid prod login broken for ~24h on 2026-05-29 (the AUTH-004 chain: client never POSTed/auth/exchange,Accept: application/jsonforced rejected preflight, api response missingAccess-Control-Allow-Credentials).POST /auth/email/start(assert 202 +{"ok":true}+ latency < 2s),OPTIONS + POST /auth/exchange(assertAccess-Control-Allow-Origin∈ allow-list ANDAccess-Control-Allow-Credentials: trueon BOTH preflight and real POST),GET /auth/mewith a probe-account bearer (assert 200 + body hasemail).instant_auth_probe_outcome_total{leg,result}counter +instant_auth_probe_latency_seconds{leg}histogram; emits structuredauth_probe_failed leg=... reason=...ERROR log +audit_logrow (kind=auth_probe_failed, actor=system:auth_probe) on every fail outcome.Rule-17 coverage block
Rule-25 observability surface
worker/internal/metrics/metrics.go::AuthProbeOutcomeTotalworker/internal/metrics/metrics.go::AuthProbeLatencySecondsinfra/newrelic/alerts/auth-probe-fail.jsoninfra/k8s/prometheus-rules.yaml::AuthProbeFailinfra/newrelic/dashboards/instanode-reliability.json(3 tiles: per-leg outcomes, fails billboard, P95 latency)infra/observability/METRICS-CATALOG.mdInfra changes live in a sibling PR on the
infrarepo (feat/auth-004-synthetic-prober) because the two repos auto-deploy on separate workflows.Synthetic identity choice (leg 2)
Chose the CORS-headers-only approach over the JWT-mint alternative — asserting that
OPTIONSandPOST /auth/exchangecarry the rightAccess-Control-Allow-OriginANDAccess-Control-Allow-Credentials: trueis the precise surface the AUTH-004 chain broke, and requires no JWT-secret distribution to the worker pod or probe-only-claim allowlist in the api (both of which would expand the secret blast radius for marginal gain over what leg 3 already covers via a real probe-account bearer).Leg 3 (
/auth/me) usesAUTH_PROBE_BEARER_TOKEN(env, k8s secret). When unset, leg 3 reportsresult="degraded"(notfail) so a missing config is visible in monitoring without triggering the page.Test plan
make gateGREEN locally (15 packages, ~95s).go test ./internal/jobs/ -run AuthProbe -short -count=1GREEN (12 tests).curl https://api.instanode.dev/healthz | jq .commit_idmatches HEAD (rule 14)./metricsscrape showsinstant_auth_probe_outcome_total{result="pass"} > 0for all 3 legs within 10 min.kubectl get prometheusrule -n instant instant-platform -o yaml | grep AuthProbeFailreturns the new rule.AUTH_PROBE_BEARER_TOKENk8s secret so leg 3 leaves thedegradedstate.🤖 Generated with Claude Code