chore(canary-phase2): generator interface + webbug complete
Phase 2 of the 18-phase plan. Lands the plugin scaffolding (Generator interface, shared transparent-pixel package, generator registry) and the first concrete generator (webbug). Phase 2 is intentionally the smallest phase — subsequent phases add slowredirect, docx, pdf, kubeconfig, envfile, and mysql generators against the same Generator interface. Implementation commits (oldest → newest):6cc724c1feat(canary): Generator interface for token-type plugins0b6e586efeat(canary): shared 43-byte transparent GIF for image triggers9c0ad749feat(canary): webbug generator (Generate + Trigger)bb95f745feat(canary): generator registry (token.Type → Generator)c39b56b9fix(canary): clear pre-audit lint debt + migrate golangci config to v21a240952fix(canary-phase2): address audit findings before rollup Deliverables vs. plan: - Generator interface (spec §6.2): Type/Generate/Trigger contract with ArtifactKind url|file|text|connection_string, Artifact discriminated by Kind, TriggerResponse value type so generators stay pure. - Pixel package: 43-byte canonical GIF89a literal exposed via pixel.Clone() (immutable source, independent slice per call) + pixel.Len() helper + ContentType const. - Webbug generator (spec §9.1 + §8.5): Generate returns KindURL artifact with baseURL/c/{id} (trailing slash trimmed). Trigger returns 200 + image/gif + 43-byte pixel + no-store cache headers. realIP precedence: CF-Connecting-IP > X-Forwarded-For (rightmost non-empty) > X-Real-IP > net.SplitHostPort(RemoteAddr). Handles IPv4/IPv6 (including bracketed form). Empty XFF entries (trailing comma, all-empty list) fall through to the next source. Nil token returns nil event + non-nil GIF response — the contract is explicit in the return shape, so the future Phase 9 handler cannot accidentally insert an event row with empty TokenID (which would fail the events → tokens FK anyway). - Generator registry: lives in backend/internal/token/generators/registry/ (sub-package, NOT the generators package itself) to break the generators → webbug → generators import cycle the implementation plan inadvertently created. registry.Config + registry.Registry + registry.Build. Phase 2 registers webbug only; cardinality test asserts exactly 1 and enumerates the 6 future types as absent. - Cross-cutting cleanup (c39b56b9): pre-audit gate found Phase 0/1 golangci-lint debt the prior phase audits didn't catch. Per fix-in-phase rule, cleared all 14 (5 errcheck, 6 golines, 1 funlen via run() → run+initTelemetry+mountRouter+gracefulShutdown split, 1 govet shadow, plus the 1 Phase-2-introduced funlen-on-test that the broken .golangci.yml v1-syntax exclude-rules failed to suppress under golangci-lint v2). Also migrated .golangci.yml to v2 schema (linters.exclusions.{paths,rules}) and added gosec G706 to excludes (false-positive log-injection on slog structured-logging call sites). Phase 2 audit — TWO agents in parallel per standing pattern: superpowers:code-reviewer → PASS (9 findings, all cleared in-phase by commit1a240952) general-purpose (spec-adherence)→ PASS (every spec contract item verified; 3 known deltas accepted: Type() refined to token.Type, registry moved to subpackage to break cycle, optionalHeader bridges plan-vs-schema pointer types) Findings cleared in-phase (from code-reviewer): MEDIUM — realIP fallback returned "IP:port"; now net.SplitHostPort MEDIUM — XFF rightmost empty entry skipped fallback; now walks right- to-left and falls through cleanly MEDIUM — pixel.TransparentGIF was exported mutable []byte; now unexported + pixel.Clone() per call LOW — nil token returned non-nil event with empty TokenID; now returns nil event (explicit contract) LOW — no IPv6 coverage in IP precedence tests; added 3 IPv6 cases + XFF IPv6 + port-less RemoteAddr NIT — gracefulShutdown swallowed shutdown errors; now errors.Join Accepted as-is (NIT, Phase 3+ concern): NIT — Artifact discriminated-union may want sealed-interface refactor once non-URL kinds land NIT — Registry exposed bare map (acceptable: read-only post-Build, Go memory model permits concurrent map reads with no writes) NIT — registry.Build accepts but ignores its Config (signature reserved for future stateful generators like mysql) Forward-looking notes for Phase 3 (NOT deferred items, just heads-up): - Phase 3's main.go wire-up cannot copy spec §6.3's `generators.BuildRegistry(cfg.Canary)` verbatim; the call is now registry.Build(registry.Config{...}) and the cfg.Canary field will need to be declared on config.Config. Deferred items: NONE. BACKLOG.md open section remains empty. Quality gates (final, post-fix): go build ./... OK go vet ./... OK go test -race ./... PASS (4 packages, 20 unit tests) go test -tags=integration -race PASS (token + event integration, ~12s testcontainers) golangci-lint run ./... 0 issues Audited: PASS.
This commit is contained in:
parent
1a240952c7
commit
8207b2747b