Commit Graph

8 Commits

Author SHA1 Message Date
shankar0123 b6a5278df1 refactor(web): drop unused imports (CodeQL #5 + #10)
Two CodeQL js/unused-local-variable alerts in one sweep — both
Note severity, both pure dead-import cleanup.

Alert #10 (web/src/pages/NotificationsPage.tsx:8):
  formatDateTime imported but only timeAgo used. Verified via
  repo-wide grep — formatDateTime appears on the import line only.
  Drop from the import statement; leave timeAgo in place.

Alert #5 (web/src/api/client.test.ts:2):
  Five unused imports in the test file's import block (the test
  file imports nearly the full API client surface):
    - acknowledgeHealthCheck
    - createPolicy
    - deleteHealthCheck
    - getHealthCheckHistory
    - updateHealthCheck
  Each appears only on the import line — verified via grep -c.
  Removing them doesn't change test coverage (the corresponding
  client functions are exported and exercised in their own tests
  elsewhere, but the integration covered by client.test.ts doesn't
  reach them yet).

Verified locally:
  tsc --noEmit: exit 0.
  grep -c on each removed symbol in its file: 0 occurrences.
  No behavioral change — pure import-list cleanup.

References:
  https://github.com/certctl-io/certctl/security/code-scanning/10
  https://github.com/certctl-io/certctl/security/code-scanning/5
Closes both alerts.
2026-05-04 05:11:23 +00:00
shankar0123 e0a3d50f5e M-029 Pass 1 batch 2: migrate 5 two-mutation pages to useTrackedMutation
Drains 10 more useMutation sites (52 -> 42). Each migration declares explicit

invalidates per the M-009 contract.

Pages migrated:

  - DashboardPage.tsx        previewDigest + sendDigest both 'noop' (read-only

                              preview / fire-and-forget email — no client cache impact)

  - DiscoveryPage.tsx        claim + dismiss both invalidate

                              [['discovered-certificates'], ['discovery-summary']]

  - NotificationsPage.tsx    markRead + requeue both invalidate [['notifications']]

  - TargetDetailPage.tsx     update + testConnection both invalidate [['target', id]]

  - TargetsPage.tsx          createTarget + deleteTarget both invalidate [['targets']]

Verification:

  legacy useMutation count   52 -> 42 (-10)

  useTrackedMutation count    4 -> 14 (+10)

Closes 14 of 56 sites toward M-029 Pass 1 completion.
2026-04-27 02:40:54 +00:00
shankar0123 55eb7135be fix(web,ci): close TS↔Go type drift across 5 entities (D-2 master)
Closes five 2026-04-24 audit findings (all P2, all category cat-f /
diff-05x06-*) by reconciling the TypeScript interfaces in
web/src/api/types.ts with the on-wire JSON shape Go's
internal/domain/*.go structs actually emit. D-1 closed the same pattern
for one entity (Certificate / ManagedCertificate); D-2 covers the
remaining five.

Per-entity verdicts (audit's "stricter side is the contract"):

  Agent       — TRIM 5 phantoms (last_heartbeat, capabilities, tags,
                created_at, updated_at). Go emits last_heartbeat_at only.
  Target      — ADD 2 (retired_at?, retired_reason?) — I-004 fields.
  DiscCert    — ADD pem_data? — real field, real Go emit, omitempty.
  Issuer      — TRIM phantom status. Go has Enabled bool only.
  Notif       — TRIM phantom subject. Go has Message string only.
  Certificate — verify-only; D-1 closure confirmed clean at recon.

Consumer fixes (same commit as the trim):
- AgentDetailPage.tsx — remove dead Capabilities + Tags sections (always
  rendered empty); replace agent.created_at/updated_at row with the
  Go-emitted registered_at; widen heartbeatStatus() to accept undefined.
- AgentsPage.tsx — same heartbeatStatus widening.
- IssuersPage.tsx + IssuerDetailPage.tsx — issuerStatus() now derives
  from `enabled` exclusively; the dead `issuer.status || 'Unknown'`
  fallback is gone.
- NotificationsPage.tsx — drop dead `|| n.subject` fallback.
- NotificationsPage.test.tsx — drop dead `subject:` from mocks.
- api/utils.ts::timeAgo widened to accept string | undefined | null.
- api/types.test.ts — Agent (I-004) fixture trimmed of the 5 phantoms.

Tests (Vitest):
- 5 new describe blocks in web/src/api/types.test.ts:
  - Agent interface (D-2 phantom-fields trim) — 2 it blocks
  - Target interface (D-2 retirement fields) — 2 it blocks
  - DiscoveredCertificate interface (D-2 pem_data ADD) — 2 it blocks
  - Issuer interface (D-2 status phantom trim) — 1 it block
  - Notification interface (D-2 subject phantom trim) — 1 it block
- Each block uses the literal-construction pattern from D-1; trimmed
  fields are pinned via excess-property comments that compile-fail when
  uncommented if a phantom is reintroduced.

CI regression guardrail:
- .github/workflows/ci.yml — existing D-1 step renamed to "Forbidden
  StatusBadge dead-key + TS phantom-field regression guard (D-1 + D-2)".
  Three new awk-windowed greps over Agent / Issuer / Notification
  interfaces in types.ts. The Agent grep includes a `grep -v
  'last_heartbeat_at'` filter to avoid false positives on the
  legitimate Go-emitted heartbeat field.

Documentation:
- CHANGELOG.md — new D-2 section above B-1 under [unreleased] with full
  Added/Removed/Audit findings closed/Known follow-ups breakdown.
- docs/architecture.md — Web Dashboard section gains a new "TS ↔ Go
  type contract rule (D-1 + D-2 closure)" paragraph capturing the
  stricter-side-wins rule and the CI guardrail it's anchored by.
- coverage-gap-audit-2026-04-24-v5/unified-audit.md — Live Tracker score
  20/47 → 25/47 (P2: 6/27 → 11/27). Per-finding  RESOLVED Status
  blocks added to all 5 diff-05x06-* entries plus the verify-only
  Certificate entry. Closed-bundle index gets D-2 row.

Verification (all gates green):
- cd web && tsc --noEmit                 → clean
- cd web && vitest run --reporter=dot    → 9 files, 302 tests passing
                                            (was 294 → +8 D-2 cases)
- cd web && vite build                   → clean
- go vet ./internal/... ./cmd/...        → clean (no Go touched)
- golangci-lint v2.11.4 run ./...        → 0 issues
- D-2 Agent guardrail dry-run            → empty (good)
- D-2 Issuer guardrail dry-run           → empty (good)
- D-2 Notification guardrail dry-run     → empty (good)
- D-2 Target ADD-shape sanity            → 2 retirement fields present
- D-2 DiscCert ADD-shape sanity          → pem_data present
- D-1 Certificate guardrail still clean  → empty (good)
- OpenAPI YAML parses                    → 89 paths

Audit findings closed:
- diff-05x06-7cdf4e78ae24 (P2, Agent TS↔Go drift)
- diff-05x06-2044a46f4dd0 (P2, Target TS↔DeploymentTarget Go drift)
- diff-05x06-85ab6b98a2f7 (P2, DiscoveredCertificate TS↔Go drift)
- diff-05x06-97fab8783a5c (P2, Issuer TS↔Go drift)
- diff-05x06-caba9eb3620e (P2, Notification TS↔NotificationEvent drift)
- diff-05x06-af18a8d7ef41 (P2) — verified clean since D-1; no edit

Deferred follow-ups:
- Issuer richer status view (enabled × test_status) — UX scope, not drift.
- Real Agent metadata (capabilities, tags) — backend feature, not drift.
- DiscoveredCertificate pem_data list-response perf — separate backend change.
2026-04-25 16:07:31 +00:00
shankar0123 675b87ba63 I-005: notification retry loop + dead-letter queue
Critical alerts can no longer be silently dropped by a transient
notifier failure. Failed notification attempts now ride an exponential
backoff retry loop, with a 5-attempt budget before promotion to the
dead-letter queue for operator intervention.

Schema (migration 000016, idempotent):
- retry_count INTEGER NOT NULL DEFAULT 0
- next_retry_at TIMESTAMPTZ
- last_error TEXT
- idx_notification_events_retry_sweep partial index
  (next_retry_at) WHERE status='failed' AND next_retry_at IS NOT NULL
  Dead rows clear next_retry_at so the index stops matching them.

Service contract:
- NotificationService.RetryFailedNotifications drives 2^n-minute
  exponential backoff capped at 1h (notifRetryBackoffCap) with
  5-attempt budget (notifRetryMaxAttempts).
- Exhaustion (RetryCount >= notifRetryMaxAttempts-1) promotes to
  status='dead' via MarkAsDead.
- Non-terminal failures record via RecordFailedAttempt.
- Success path promotes to 'sent' without touching retry_count
  (audit preserves "delivered on attempt N").
- Missing-notifier branch defensively promotes to 'sent' to avoid
  wedging a row on a deleted channel.
- RequeueNotification operator escape hatch atomically resets
  retry_count -> 0, next_retry_at -> NULL, last_error -> NULL,
  status -> pending via notifRepo.Requeue.

Scheduler:
- New always-on notificationRetryLoop wired into the base loop set at
  CERTCTL_NOTIFICATION_RETRY_INTERVAL (default 2m).
- sync/atomic.Bool idempotency guard.
- sync.WaitGroup shutdown drain via WaitForCompletion.

StatsService:
- SetNotifRepo setter pattern preserves 9 pre-existing
  NewStatsService call sites (main.go + stats_test.go + 8 digest
  tests) without touching the constructor signature.
- DashboardSummary.NotificationsDead populated via
  notifRepo.CountByStatus(ctx, "dead") — nil-safe when unwired
  (reports zero on systems without a notification repository).
- CountByStatus error is non-fatal (dashboard summary is
  best-effort for this field).
- Prometheus certctl_notification_dead_total counter emitted from
  the same snapshot.

Handler:
- New POST /api/v1/notifications/{id}/requeue endpoint.
- dead status surfaces to MCP + CLI.

Frontend:
- NotificationsPage gains two-tab toolbar ("All" / "Dead letter")
  with queryKey: ['notifications', activeTab] so switching tabs
  doesn't serve stale data until the 30s refetch.
- Dead rows surface "Retry {n}/5" + truncated last_error with
  full-text title tooltip.
- Requeue mutation wrapped as
    mutationFn: (id: string) => requeueNotification(id)
  to prevent react-query v5's positional context argument from
  leaking into the API client — pinned against future refactors
  by strict-match toHaveBeenCalledWith('notif-dead-001') in
  NotificationsPage.test.tsx:181.

Closes I-005.
2026-04-19 15:17:27 +00:00
shankar0123 50c520e1ff feat: dashboard theme overhaul — light content area with branded teal sidebar
Complete frontend visual redesign using certctl logo color palette:
- Deep teal sidebar (#0c2e25) with prominent centered logo (64px in white pill)
- Light content area (#f0f4f8) with white cards and visible borders
- Brand colors from logo: teal (#2ea88f), blue (#3b7dd8), orange (#e8873a), green (#4ebe6e)
- Inter + JetBrains Mono typography, colored stat card top borders
- All 17 pages + 7 components updated (25 files, ~700 lines changed)
- 15 new dashboard screenshots replacing old dark theme screenshots
- Prometheus metrics e2e test added, integration test mock fixes
- Docs updated: architecture.md theme description, testing-guide.md DNS-PERSIST-01 coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 23:27:42 -04:00
shankar0123 73c6bd1416 feat: add frontend action buttons, fix notification auth bug, add 53 Vitest tests
Bug fix:
- markNotificationRead was using raw fetch() without auth headers,
  bypassing the shared client's Authorization header. Moved to
  api/client.ts to use fetchJSON with proper auth.

New action buttons:
- CertificatesPage: "New Certificate" modal with form fields
- CertificateDetailPage: "Deploy" button with target selector modal,
  "Archive" button with confirmation
- IssuersPage: "Test Connection" and "Delete" per-row actions
- TargetsPage: "Delete" per-row action
- PoliciesPage: "Enable/Disable" toggle and "Delete" per-row actions

New API client functions:
- updateCertificate, archiveCertificate, registerAgent,
  createPolicy, updatePolicy, deletePolicy, getPolicyViolations,
  createIssuer, testIssuerConnection, deleteIssuer,
  createTarget, deleteTarget, markNotificationRead

Frontend tests (53 tests, 2 files):
- client.test.ts: 35 tests covering all API endpoints, auth headers,
  401 handling, error parsing, HTTP methods, request bodies
- utils.test.ts: 18 tests covering formatDate, formatDateTime,
  timeAgo, daysUntil, expiryColor

CI: Added "Run Frontend Tests" step to frontend-build job

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 00:05:21 -04:00
shankar0123 f6139252e1 Implement M6: functional GUI views, GitHub Actions CI
Wire all remaining dashboard views to real API: agent detail page
with heartbeat status and capabilities, audit trail with time range/
actor/resource filters, notifications with grouped-by-cert view and
read/unread state, policies with severity summary bar, new issuers
and targets list views. Add GitHub Actions CI with parallel Go and
Frontend jobs. Update Makefile with test-cover and frontend-build
targets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 11:12:49 -04:00
shankar0123 9e6756d02f Implement M5: hardening, input validation, and Vite+React+TS dashboard
Backend hardening:
- Fix 6 nginx.go non-constant format string build errors
- Add validation.go with hostname, PEM, and enum validators
- Apply input validation to all POST/PUT handlers (certificates,
  agents, CSR, policies, teams, owners, targets, issuers)
- Fix unchecked JSON decode in TriggerDeployment handler

Frontend (Vite + React + TypeScript):
- Migrate from single-file SPA to proper build pipeline
- 7 pages: Dashboard, Certificates (list+detail), Agents, Jobs,
  Notifications, Policies, Audit Trail
- TanStack Query for server state with auto-refetch intervals
- Certificate detail with version history and renewal trigger
- Job cancellation, status/type filtering, expiry countdowns
- Reusable components: DataTable, StatusBadge, ErrorState, PageHeader
- Dark theme with Tailwind CSS, sidebar nav via React Router

Server integration:
- Go server serves web/dist/ (Vite output) with SPA fallback
- Falls back to web/index.html for legacy mode
- .gitignore updated for web/node_modules/ and web/dist/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:19:19 -04:00