chore(canary-phase9): wire-up complete — API + middleware + Turnstile + token service

Phase 9 is the wire-up phase: brings together everything the generator
phases produced into a working HTTP service plus the TCP mysql
listener. All 7 generators are now reachable end-to-end through
HTTP routes (or TCP for mysql), and POST /api/tokens creates tokens
backed by a real registry.

Implementation commits in this phase:
  - c988e4b9 feat(canary): middleware additions (realip, fingerprint,
    recovery)
  - 537b0e3f feat(canary): turnstile verifier + middleware
  - 019bfda4 feat(canary): token service + contract relocation
  - 0a456f97 feat(canary): token HTTP handlers (API + trigger routes)
  - 8d1016b8 feat(canary): full wire-up + webbug refactor + mysql
    goroutine
  - 6c370825 fix(canary-phase9): plumb mysql public host/port through
    to generator

Deliverables:

middleware/
  - realip.go: RealIP + OptionalHeader (promoted from webbug)
  - fingerprint.go: ExtractFingerprint (sha256[:16] of realIP|UA) +
    KeyByFingerprint (rate-limit key builder)
  - recovery.go: defer recover + 500 JSON envelope + request_id log
  - turnstile.go: header-or-body extraction + verifier delegation

turnstile/
  - verifier.go: cloudflare siteverify call + 5-min fingerprint cache +
    dev-mode bypass when SecretKey empty + 10s HTTP timeout

token/
  - contract.go (NEW LOCATION): Generator interface + Artifact +
    ArtifactKind + TriggerResponse moved from generators package
    (resolved long-standing import cycle); generators/generator.go
    now re-exports as type aliases for backwards compatibility
  - service.go: Create (validation + ID/manage_id gen + registry
    dispatch + repo persist), GetByID, GetByManageID,
    IncrementTriggerCount, TriggerURL/ManageURL builders
  - handler.go: RegisterAPIRoutes (GET /tokens/types, POST /tokens),
    RegisterTriggerRoutes (GET /c/{id}, POST /c/{id}/fingerprint,
    HandleFunc /k/{id}, /k/{id}/* for any method)
  - types.go: TypeDescriptors() — static 7-type catalog for the
    /tokens/types endpoint

cmd/canary/main.go: full wire-up per spec §11.1
  - global: RequestID → Logger → Recovery → SecurityHeaders
  - /healthz: no further mw
  - /c/{id}, /c/{id}/fingerprint, /k/{id}, /k/{id}/*: OUTSIDE /api
    (no CORS, no rate limit, no Turnstile — we want every attacker
    hit recorded per spec §11.1 line 1552)
  - /api: CORS → RateLimit (KeyByFingerprint, FailOpen=true)
  - /api/tokens/types: read-only public
  - /api/tokens POST: + TurnstileVerify middleware
  - mysql goroutine via spawnMySQLListener iff cfg.MySQL.Enabled
    (closes Phase 8 task 8.5 deferral); uses mysqlTokenLookup +
    mysqlEventRecorder adapters
  - directEventRecorder bridges token.EventRecorder to
    event.Repository + token.Repository (Phase 10 swaps in
    event.Service for dedup + async notify)

webbug refactor (task 9.8): deletes local realIP/lastNonEmptyXFF/
optionalHeader copies, uses middleware helpers. Other 5 generators
still have their copies (sanctioned per handoff).

config additions: Canary {BaseURL, ManageURL}, Turnstile {SecretKey,
SiteKey}, MySQL {Enabled, Addr, PublicHost, PublicPort}. Env keys +
defaults wired.

.golangci.yml: adds gosec G107 + G704 to global excludes (SSRF taint
analysis on operator-configured outbound HTTP). Documented as
policy-level (matches G104/G706 precedent). Phase 10's webhook sender
will validate user-supplied URLs at the call site.

~50 new test cases across the 5 new packages:
  - middleware: 16 (IP precedence + fingerprint + recovery +
    Turnstile mw incl. header/body/preservation)
  - turnstile: 14 (bypass + cache hit/miss + failure + nil-Redis +
    network error)
  - service: 15 (ID generation + repo persistence + metadata
    validation per type + rollback on generator failure + URL
    builders + ~30-call distinctness)
  - handler: 10 (envelope + happy path + bad JSON + validation +
    trigger known/unknown/disabled token + ArtifactJSON discriminator)
  - registry: +1 (MySQLUsesConfiguredPublicAddress regression guard)

Audit outcome (2 agents in parallel per standing pattern):
  - superpowers:code-reviewer: PASS-WITH-NITS (0 BLOCKER, 3 SHOULD-FIX,
    5 NITs). One actionable SHOULD-FIX (mysql public address ignored)
    cleared in 6c370825 before rollup. Other two SHOULD-FIX items
    (rate-limit create-tier; scope G107 exclude) deferred to Phase 10
    with explicit rationale.
  - general-purpose spec-adherence vs §6/§8/§11: PASS-WITH-NITS. All
    12 tasks discharged or properly deferred (9.9 → Phase 10, 9.12 =
    this rollup). All §11.1 middleware order layers verified. All
    §8.2 request/response shapes verified. §11.2 fingerprint and
    §11.4 Turnstile both match spec.

Pre-rollup gate clean at HEAD 6c370825:
  - go build / vet / test -race / test -tags=integration /
    golangci-lint run — all clean
  - zero //nolint pragmas
  - BACKLOG open section still _(none)_
  - go.mod surface unchanged (no new direct deps in Phase 9)

DEFERRED (sanctioned):
  - Task 9.9 testcontainers integration test → Phase 10 (alongside
    event.Service + notify.Service end-to-end coverage)
  - §11.3 create-tier rate-limit nesting → Phase 10 (Turnstile
    already provides spam protection at the create path)
  - Scope gosec G107/G704 to specific packages → Phase 10 (need
    webhook sender first to validate the call-site validation
    pattern)
  - mysql_username metadata write in token.Service → Phase 10 (mysql
    trigger lookup is by token ID, not username — current Phase 8
    impl already handles this in the generator)
  - FingerprintRecorder real wiring (currently nil → 204) → Phase 10
  - /api/m/{manage_id} GET/DELETE → Phase 11 (manage page API)

Forward state for Phase 10 (event service + dedup + notify dispatcher):
swaps directEventRecorder + mysqlEventRecorder for event.Service.Record,
brings notify.Service with Telegram + webhook senders, Redis SetNX
dedup gate, retention loop. Then Phase 11 wires /api/m/{manage_id}
manage page endpoints, Phase 12 wires admin + OperatorBearer, Phase 13
wires GeoIP.
This commit is contained in:
CarterPerez-dev 2026-05-13 15:27:55 -04:00
parent 6c37082598
commit 1fe61345ef

Diff Content Not Available