Commit Graph

5 Commits

Author SHA1 Message Date
shankar0123 c4ed3da30b fix(ci): Sprint 6 CI follow-up — staticcheck ST1021 + tenant-query baseline + skip inventory
Sprint 6 push (commits 43836ac + 663b14b) tripped three CI guards.
Fixing all three in this single follow-up — each is a small,
mechanical correction that doesn't change behavior:

1. staticcheck ST1021: AuditChainSnapshot doc comment was on the
   wrong type.

   internal/service/audit_chain_metric.go:91 had:
     // Snapshot returns the current counter state for the Prometheus
     // exposer. Reads use atomic loads — no mutex.
     type AuditChainSnapshot struct { ... }

   The comment described Snapshot() (the method on AuditChainCounter)
   but sat directly above the AuditChainSnapshot struct. staticcheck
   ST1021 requires exported-type comments to start with the type's
   name + optional leading article. Rewrote to lead with
   "AuditChainSnapshot is the point-in-time view ...".

2. multi-tenant-query-coverage: baseline drifted 31 → 32 because
   Sprint 6 COMP-002-RETENTION added UserRepository.ListDeactivatedBefore
   at internal/repository/postgres/user.go:191 — legitimately
   tenant-spanning by design.

   The retention policy is control-plane-wide (one
   CERTCTL_USER_RETENTION_WINDOW for the whole deployment, not
   per-tenant). The scheduler's userRetentionLoop walks every
   tenant's deactivated users on the same tick. A per-tenant
   tenant_id filter would require the scheduler to iterate every
   tenant — more code for equivalent semantics.

   Per the guard's own documentation (option b), legitimately
   tenant-spanning queries get an inline rationale comment + a
   baseline lift. Both delivered:
     - Inline comment block on the SELECT in user.go::ListDeactivatedBefore.
     - BASELINE_COUNT 31 → 32 in
       scripts/ci-guards/multi-tenant-query-coverage.sh, with the
       Sprint 6 rebase entry added to the rebase-history comment.

3. skip-inventory-drift: docs/testing/skip-inventory.md was stale.
   COMP-001-HASH added three new t.Skip sites in
   internal/repository/postgres/audit_chain_test.go (the three
   testing.Short() gates on the testcontainers integration tests).
   Re-ran ./scripts/skip-inventory.sh to regenerate the doc —
   totals went from 144 → 147 sites + 78 → 82 short-mode guards.

Verified locally:
  bash scripts/ci-guards/multi-tenant-query-coverage.sh      (clean)
  bash scripts/ci-guards/skip-inventory-drift.sh              (clean)
  go vet ./...                                                (clean)
  staticcheck ./internal/service/...                          (clean)

Closes the three Sprint 6 CI failures. The next CI run should
green out.
2026-05-16 06:24:09 +00:00
shankar0123 c40690e42d docs(testing): regenerate skip-inventory after SEC-001 types_test.go edit (CI guard skip-inventory-drift)
SEC-001's TestOIDCProvider_Validate_RejectsSSRFIssuer addition
in internal/auth/oidc/domain/types_test.go shifted an existing
t.Skip site from line 186 → line 221. The auto-generated
inventory at docs/testing/skip-inventory.md still pointed at
the old line, so scripts/ci-guards/skip-inventory-drift.sh
failed the build.

Regenerated via scripts/skip-inventory.sh and bumped the
'> Last reviewed:' header. Inventory now matches the live
tree exactly.
2026-05-16 04:15:35 +00:00
shankar0123 c8985cf868 fix(ratelimit): Hotfix #5 — Postgres timestamptz[] scan + skip-inventory drift
Two CI hotfixes surfaced by master CI on 29cb13e7 (Sprint 13.6 tip
before the Sprint 13.7 closure landed):

1. TestRateLimit_PostgresBackend_CapEnforcedAcrossReplicas failed with
   "pq: scanning to time.Time is not implemented; only sql.Scanner".
   Root cause: time.Time does not implement sql.Scanner, and lib/pq's
   pq.GenericArray scan path calls element-Scan() directly rather than
   database/sql's convertAssign (which DOES support time conversions).
   So `pq.Array(&[]time.Time{})` reliably fails on read even though
   the symmetric write `pq.Array([]time.Time{...})` works (the write
   path uses driver.Value() which time.Time implements).

   Fix: cast the timestamptz[] to a text[] of canonical ISO 8601 UTC
   strings at the SQL boundary via to_char(t AT TIME ZONE 'UTC',
   'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'), read via pq.StringArray (well-
   supported), and parse Go-side with layout "2006-01-02T15:04:05.000000Z".
   The format is fully deterministic regardless of the session's
   DateStyle or TimeZone settings.

   Touched: internal/ratelimit/postgres_sliding_window.go (Step 2 of
   the Allow() transaction — locking + read).

   Falsifiable proof on CI: the failing test
   TestRateLimit_PostgresBackend_CapEnforcedAcrossReplicas
   (100 concurrent Allow calls / 3 replicas / cap=10) must now produce
   exactly 10 succeed / 90 ErrRateLimited. Pre-fix it produced 1 / 0
   because every Allow after the first crashed on Scan.

2. skip-inventory-drift.sh CI guard turned red because Sprint 13.2
   added two new t.Skip sites:

     internal/ratelimit/equivalence_test.go:80
       t.Skip("race-style test under -short")
     internal/ratelimit/equivalence_test.go:88
       t.Skip("postgres equivalence tests require testcontainers;
              skipped under -short")

   The inventory at docs/testing/skip-inventory.md is auto-generated
   by scripts/skip-inventory.sh and must be re-generated alongside
   any t.Skip churn. Sprint 13.2 missed the regeneration.

   Fix: re-ran scripts/skip-inventory.sh. Totals walked
   142 → 144 sites; testing.Short() guards 76 → 78. The two new
   entries land in the internal/ratelimit section.

Verification (local sandbox, all clean):
  $ bash scripts/ci-guards/skip-inventory-drift.sh
    skip-inventory-drift guard OK: docs/testing/skip-inventory.md
    matches the live tree
  $ bash scripts/ci-guards/openapi-handler-parity.sh
    openapi-handler-parity: clean.
  $ bash scripts/ci-guards/openapi-rest-deferred-monotonic.sh
    openapi-rest-deferred-monotonic: clean — rest-deferred = 0,
    baseline = 0.
  $ gofmt -l internal/ratelimit/postgres_sliding_window.go
    (no output)
  $ go vet ./internal/ratelimit/
    (no output)

The Postgres rate-limit fix's full falsifiable proof
(TestRateLimit_PostgresBackend_CapEnforcedAcrossReplicas) cannot be
exercised in the sandbox (no docker for testcontainers); CI on the
amd64 runner will re-run it on this push. The diagnosis is verified
against lib/pq source semantics and the fix uses only well-supported
primitives (pq.StringArray + canonical to_char output + time.Parse).
2026-05-14 13:26:47 +00:00
shankar0123 558d350933 fix(ci): teach 3 CI guards about Phase 9 sibling-file splits
Two CI guards on origin/master failed against the Sprint-12 commit
(30940108) because they didn't know about new files introduced by
earlier Phase 9 sprints. Both are pure mechanical relocation
fall-out — no actual regression in functionality.

1. scripts/ci-guards/no-new-synthetic-admin.sh — A-8 guard
====================================================================
Sprint 5 (commit 51f9cf13) extracted the Auth-family from
internal/config/config.go to internal/config/auth.go. The 4
'actor-demo-anon' references moved with the Auth-family code:

  - Line 255: 'actor-demo-anon is wired with AdminKey=true'
    documentation comment alongside the AdminKey wiring narrative.
  - Lines 283/289/293: residual-grants detector + cleanup SQL
    examples explaining why 'ar-demo-anon-admin' is reserved.

These are the SAME comments that were previously in config.go (which
IS in the allowlist), just relocated to the new sibling file. The
references were always present in the codebase; the A-8 guard was
just unaware of the new file location.

Fix: add './internal/config/auth.go' to the ALLOWLIST with a rationale
comment pointing at commit 51f9cf13.

Local verification: A-8 guard PASS — actor-demo-anon references
confined to the declared 19-entry allowlist (was 18, now 19).

2. internal/ciparity/surface_parity_test.go — mcpToolFiles list
====================================================================
Sprint 10 (commit fbe053aa) split internal/mcp/tools.go (1867 LOC,
121 mcp.AddTool registrations) into six tool-domain sibling files:

  tools_certificates.go (22 tools — cert + CRL/OCSP + renewal + verify)
  tools_agents.go       (16 tools — agents + agent groups)
  tools_resources.go    (40 tools — issuers + targets + policies +
                                    profiles + teams + owners +
                                    notifications + intermediate-CAs)
  tools_jobs.go         (9  tools — jobs + approvals)
  tools_discovery.go    (10 tools — network-scan + discovery)
  tools_admin.go        (24 tools — audit + stats + digest + metrics
                                    + health + health-check)

The TestSurfaceParity_MCPToolCatalogue hard-gate counts mcp.AddTool
registrations across mcpToolFiles() — a hard-coded 5-file list. After
the split, only 34 tools sat in the 5 known files (tools.go itself
went to 0 tools post-split; only the 4 pre-existing tools_*.go
siblings carried any). The actual cross-file count is 155 (above
the 150 floor).

Fix: expand mcpToolFiles() to include the 6 new Sprint-10 sibling
files. Doc-comment explains the Sprint-10 split + the union-of-files
intent.

Local verification:
  PASS: TestSurfaceParity_MCPToolCatalogue
    MCP tool catalogue: 155 tools (baseline floor 150)

3. docs/testing/skip-inventory.md — line-number drift
====================================================================
Adding the 8-line doc-comment to mcpToolFiles() (item 2) shifted the
location of readFileOrSkip from line 97 to line 113 in
surface_parity_test.go. The skip-inventory.md is auto-generated and
records every t.Skip() site with its file:line; the
skip-inventory-drift CI guard re-runs the generator and diffs.

Fix: bump the inventory entry from :97 to :113. One-line tracking
update; same skip site, new line number. (No t.Skip() was added or
removed.)

Behavior preservation contract
==============================
- Zero runtime change. All three diffs touch only CI-guard
  metadata (allowlist string, file-list slice, doc line-number).
- A-8 guard re-runs clean post-fix.
- TestSurfaceParity_MCPToolCatalogue runs and reports 155 tools.
- skip-inventory drift detection re-pins to the live line number.
- gofmt + go vet + staticcheck remain clean on the touched files
  (verified pre-commit; the sandbox /sessions partition is full so
  the broader 'all guards' loop was interrupted on a tmpfile write,
  not on a real regression — the deterministic fix above matches
  the CI failure output byte-for-byte).

Closes: CI failures on commit 30940108 across Frontend Build (A-8
guard) + Go Build & Test (TestSurfaceParity_MCPToolCatalogue).
2026-05-14 11:04:32 +00:00
shankar0123 02438ad9e1 ci: floor raise + doc drift (Phase 3 closure — TEST-H1/H2/M1/M2/M3/M4/L1, ARCH-H3/L1/L2/L3/L4)
Twelve findings from the architecture diligence audit's Phase 3 bundle
closed in one PR. All touch the CI workflows + small doc-drift fixes
across the production Go tree + migration headers.

CI workflow changes
====================

TEST-H1 — Race detection on ./... -short
  .github/workflows/ci.yml:106 was a 9-package explicit list. Audit
  finding TEST-H1 flagged that 25+ packages (internal/auth/*,
  internal/repository/*, internal/mcp, internal/scep, internal/pkcs7,
  internal/api/router, internal/api/acme, internal/cli, internal/cms,
  internal/config, internal/deploy, internal/integration,
  internal/ratelimit, internal/secret, internal/trustanchor, all of
  cmd/) silently dropped off race coverage.
  Post-fix: 'go test -race -short ./... -count=1 -timeout 600s'.
  76 testing.Short() guards already cover testcontainers + live-DB
  integration suites, so -short keeps the long-running tests out.

TEST-H2 — Cross-platform build matrix
  New 'cross-platform-build' job in ci.yml. Matrix:
  ubuntu-latest + windows-latest + macos-latest, fail-fast: false.
  Builds cmd/server + cmd/agent + cmd/cli + cmd/mcp-server on each.
  Catches Windows-specific regressions (path separators, file
  permissions, exec.Command semantics) the pre-Phase-3 Ubuntu-only
  CI missed.

TEST-L1 — actions/setup-go cache: true (explicit)
  setup-go v5 defaults cache: true; making it explicit so a future
  setup-go upgrade can't silently flip it. Re-runs hit the Go module
  + build cache instead of recompiling cold.

TEST-M1 — Mutation-testing floor at 55%
  security-deep-scan.yml::go-mutesting step rewritten. Removed
  continue-on-error + per-package '|| true'. New post-loop check
  extracts every 'The mutation score is X.YZ' line and fails the
  step if any package drops below 0.55. Floor rationale: starter
  ratio catches major regressions without rejecting the audit's
  'this is OK' steady state; raise quarterly.

TEST-M2 — 3 advisory deep-scan gates promoted to blocking
  Removed continue-on-error: true from:
    - gosec (filtered to G201/G202/G304/G108 high-signal rules:
      SQL-injection + path-traversal + pprof-exposed)
    - osv-scanner (multi-ecosystem CVE; complements govulncheck
      which is already blocking in ci.yml)
    - trivy image scan (--severity HIGH,CRITICAL --exit-code 1)
  continue-on-error count: 15 → 11.
  ZAP / schemathesis / nuclei / testssl stay advisory because their
  false-positive rates on https://localhost:8443-targeted DAST runs
  are high.

TEST-M3 — Playwright harness stub
  web/package.json adds '@playwright/test' devDep + 'e2e' / 'e2e:install'
  npm scripts. web/playwright.config.ts ships single chromium project
  with webServer block pointing at 'npm run dev'. web/src/__tests__/
  e2e/smoke.spec.ts proves the harness wires through. The full 15-flow
  suite ships in frontend-design-audit Phase 8 (TEST-H1 in THAT audit);
  this is the wiring + a single smoke test as the regression floor.
  New Makefile target: 'make e2e-test'.

Doc/code drift fixes
====================

TEST-M4 + ARCH-L2 — Skip inventory artifact + CI guard
  scripts/skip-inventory.sh walks every t.Skip site under cmd/ +
  internal/ + deploy/test/ and emits docs/testing/skip-inventory.md
  grouped by package with file:line:expression triples. Current
  inventory: 142 t.Skip sites, 76 testing.Short() guards.
  scripts/ci-guards/skip-inventory-drift.sh regenerates and fails on
  diff (excluding the 'Last reviewed' timestamp line which drifts
  daily). The Markdown is the canonical acquisition-diligence artifact
  for 'what tests are being skipped and why.'

ARCH-H3 — MCP catalogue floor reconciliation
  Audit framing was '121 vs floor 150 — doc/code drift.' Live count
  via the test's actual regex over all 5 tool files (tools.go +
  tools_audit_fix.go + tools_auth.go + tools_auth_bundle2.go +
  tools_est.go): 155 unique 'Name: "certctl_*"' declarations.
  Pre-Phase-3 audit measured tools.go in isolation (121) and missed
  the other 4 files (+34 unique names). The test at
  internal/ciparity/surface_parity_test.go::TestSurfaceParity_MCP
  passes today (155 ≥ 150). Added a clarifying comment near
  mcpBaselineFloor explaining the measurement scope so future
  reviewers don't repeat the audit's framing error.
  STATUS: stale — no code drift, just a measurement scoping error in
  the audit.

ARCH-L1 — panic() rationale comments
  5 panic sites in production Go (excluding _test.go):
    - internal/repository/postgres/tx.go:84
    - internal/service/issuer.go:861 (mustJSON)
    - internal/service/est.go:728 (mustParseTime)
    - internal/service/acme.go:1288 (rand source failure — already documented)
    - internal/pkcs7/certrep.go:270 (OID marshal — already documented)
  Added ARCH-L1 rationale comments to the 3 sites that didn't have
  them. All 5 are defensible impossible-path / rethrow / hardcoded-
  constant guards.

ARCH-L3 — Migration IF-NOT-EXISTS carve-outs
  4 migrations skip the literal 'IF NOT EXISTS' token but ARE
  idempotent via different Postgres patterns:
    - 000014_policy_violation_severity_check.up.sql: ALTER TABLE
      ADD CONSTRAINT CHECK doesn't accept IF NOT EXISTS; idempotency
      via DROP CONSTRAINT IF EXISTS preamble.
    - 000018_audit_events_worm.up.sql: CREATE OR REPLACE FUNCTION
      + DROP TRIGGER IF EXISTS + CREATE TRIGGER + DO $$ pg_roles
      existence check. CREATE TRIGGER doesn't take IF NOT EXISTS.
    - 000030_rbac_admin_perms.up.sql: INSERT ... ON CONFLICT DO NOTHING.
    - 000039_audit_crit1_perms.up.sql: same INSERT + ON CONFLICT pattern.
  Added ARCH-L3 header comments to each explaining the carve-out so
  reviewers don't flag the missing literal token.
  STATUS: largely stale — migrations are already idempotent.

ARCH-L4 — TODO/FIXME → see #<descriptor>
  5 TODOs rewritten to the allowed 'see #<descriptor>' pattern:
    - internal/repository/postgres/auth.go:220 → see #bundle-2-scope-fk
    - internal/connector/discovery/gcpsm/gcpsm.go:547 → see #gcpsm-pagination
    - internal/service/audit.go:244 → see #audit-pagination-count
    - internal/service/job.go:295, 299 → see #validation-job-impl
  New CI guard scripts/ci-guards/no-todo-in-prod.sh grep-fails any
  new TODO/FIXME in cmd/ + internal/ (excluding _test.go); allows
  'see #N' / 'see #<descriptor>' patterns.

Sandbox limitation
==================
The 6.1 GB certctl working tree fills the sandbox volume; go1.25.10
toolchain download fails with 'no space left on device' (sandbox has
1.25.9; go.mod requires 1.25.10). Local 'go test' / 'go build' NOT
run in this commit. Operator must run 'make verify' on their
workstation before push per CLAUDE.md operating rules.

The smoke.spec.ts NOT executed in the sandbox (no chromium installed).
Operator runs 'cd web && npm install && npx playwright install
--with-deps chromium && npm run e2e' on first wire-up.

All CI guards (no-todo-in-prod, skip-inventory-drift, G-3
env-docs-drift, doc-rot-detector, and every existing guard) verified
clean by running each individually.

Closes: cowork/certctl-architecture-diligence-audit.html#fix-TEST-H1,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-H2,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-M1,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-M2,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-M3,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-M4,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-L1,
        cowork/certctl-architecture-diligence-audit.html#fix-ARCH-H3,
        cowork/certctl-architecture-diligence-audit.html#fix-ARCH-L1,
        cowork/certctl-architecture-diligence-audit.html#fix-ARCH-L2,
        cowork/certctl-architecture-diligence-audit.html#fix-ARCH-L3,
        cowork/certctl-architecture-diligence-audit.html#fix-ARCH-L4
2026-05-13 20:10:08 +00:00