mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:01:32 +00:00
9dc0742e77
Five audit findings, all category cat-d or cat-f, all rooted in two
frontend files. The dashboard silently lied:
cat-d-359e92c20cbf [P1, primary] — Agent: 'Stale' dead key + 'Degraded'
neutral fallthrough
cat-d-9f4c8e4a91f1 [P2] — Notification: 'dead' missing
cat-d-1447e04732e7 [P3] — Cert: 'PendingIssuance' dead key
cat-f-cert_detail_page_key_render_fallback [P2] — render-site reads
cert.key_algorithm directly
cat-f-ae0d06b6588f [P2] — Certificate TS phantom fields (root cause)
Pre-D-1, agents in the only Go AgentStatus that means 'needs operator
attention' (Degraded) rendered as default neutral grey because StatusBadge
mapped 'Stale' (a key Go has never emitted) to yellow. Dead-letter
notifications visually equated with 'read' (operator-acknowledged). The
Certificate badge map carried a 'PendingIssuance' key no Go enum emits.
CertificateDetailPage's Key Algorithm and Key Size rows always rendered
'—' even when the data was a single fetch away — the lookup went through
cert.key_algorithm / cert.key_size directly, both phantom Certificate TS
fields. Trim the TS type so the missing-data case is explicit; fix the
render site to use latestVersion?.field; pin the contract with a 38-case
Vitest property test that walks every Go enum.
StatusBadge (web/src/components/StatusBadge.tsx)
- Drop 'Stale' (Agent dead key) + 'PendingIssuance' (Cert dead key).
- Add 'Degraded' (Agent → badge-warning) + 'dead' (Notification → badge-danger).
- Add leading docblock naming Go-side source-of-truth file for every
status family and pointing at the property test as regression vector.
Property test (web/src/components/StatusBadge.test.tsx — 38 cases)
- Iterates every Go-emitted enum value (AgentStatus, CertificateStatus,
JobStatus, NotificationStatus, DiscoveryStatus, HealthStatus) plus the
two frontend-synthesized Enabled/Disabled labels, asserts every value
gets a non-default class (or an explicit 'badge badge-neutral' for the
five intentionally-neutral terminal values: Archived, Cancelled,
Dismissed, read, unknown).
- Negative assertions: 'Stale' and 'PendingIssuance' must fall through
to the dictionary default — re-adding either key surfaces here.
- Specific UX-correctness assertions: 'dead' → badge-danger,
'Degraded' → badge-warning.
- Unknown-status fallthrough preserves label text.
Certificate TS trim (web/src/api/types.ts)
- Drop serial_number?, fingerprint_sha256?, key_algorithm?, key_size?,
issued_at? from Certificate. Go's ManagedCertificate has never carried
these — they live on CertificateVersion. Post-trim a cert.X access for
any of the five fields is a TS compile error.
- Leading docblock cross-references the closure rationale and the
latestVersion fallback pattern.
Render-site fix (web/src/pages/CertificateDetailPage.tsx)
- Key Algorithm / Key Size rows now read latestVersion?.key_algorithm /
latestVersion?.key_size, mirroring the existing latestVersion fallback
used a few lines above for serial_number / fingerprint_sha256.
- The same edit also tightened the serial / fingerprint / issued_at
derivations to drop the now-impossible 'cert.X || latestVersion?.X'
cert-side leg (cert.serial_number is a TS error post-trim).
Type-test regression (web/src/api/types.test.ts)
- Certificate literal construction pinned post-trim — adding any of the
five fields back makes the literal an excess-property TS error.
- Sibling CertificateVersion literal pinning the trimmed fields still
live on the version envelope (so the CertificateDetailPage fallback
path can't break).
OpenAPI (api/openapi.yaml)
- ManagedCertificate schema unchanged — was already correct (no phantom
fields). Added a leading comment cross-referencing the D-5 closure for
future readers.
CI guardrail (.github/workflows/ci.yml)
- 'Forbidden StatusBadge dead-key + Certificate phantom-field regression
guard (D-1)'. Two grep blocks: catches Stale/PendingIssuance map
literals in StatusBadge.tsx; uses an awk-scoped window over the
'export interface Certificate {' block in types.ts to catch the five
phantom fields reappearing while explicitly excluding CertificateVersion
(which legitimately carries them). Comments + test files exempt.
Verification
- Backend build/vet/test -short -race all clean across handler/router/
middleware packages.
- Frontend tsc --noEmit clean.
- Vitest 256 → 296 tests (+40: 38 from new StatusBadge test, 2 from D-5
Certificate trim regression in types.test.ts).
- OpenAPI YAML parses (87 paths).
- Both CI guardrail patterns clear on the post-fix tree; both fire
against synthetic regression patterns (re-add Stale → fires; re-add
serial_number? to Certificate → fires).
Out of scope (deferred)
- diff-05x06-* type drifts for Agent/DeploymentTarget/Notification/
DiscoveredCertificate/Issuer TS interfaces. Per-type field-by-field
Go ↔ TS diff is codegen-shaped, not edit-shaped — warrants its own
D-2 master prompt. Noted in CHANGELOG follow-ups section.
81 lines
4.0 KiB
TypeScript
81 lines
4.0 KiB
TypeScript
// StatusBadge — single source of truth for the certctl dashboard's
|
|
// per-status color mapping. Keys are the EXACT wire values Go emits
|
|
// (case-sensitive). Update this file when a new status value lands on
|
|
// the Go side; StatusBadge.test.tsx walks every value and will go red
|
|
// before users see a default-grey "what is happening?" badge.
|
|
//
|
|
// D-1 master closure (cat-d-359e92c20cbf, cat-d-9f4c8e4a91f1,
|
|
// cat-d-1447e04732e7, cat-f-cert_detail_page_key_render_fallback,
|
|
// cat-f-ae0d06b6588f) fixed the pre-master drift:
|
|
// - Agent: 'Stale' (never emitted) → 'Degraded' (real value);
|
|
// `internal/domain/connector.go::AgentStatusDegraded = "Degraded"`.
|
|
// - Notification: added 'dead' (was falling through to neutral);
|
|
// `internal/domain/notification.go::NotificationStatusDead = "dead"`.
|
|
// - Certificate: dropped dead 'PendingIssuance' key — the real
|
|
// `CertificateStatusPending = "Pending"` is mapped under Job
|
|
// statuses below.
|
|
//
|
|
// Source-of-truth references (re-verify if the Go enum changes):
|
|
// - internal/domain/connector.go::AgentStatus*
|
|
// - internal/domain/certificate.go::CertificateStatus*
|
|
// - internal/domain/job.go::JobStatus*
|
|
// - internal/domain/notification.go::NotificationStatus*
|
|
// - internal/domain/discovery.go::DiscoveryStatus*
|
|
// - internal/domain/health_check.go::HealthStatus*
|
|
//
|
|
// Issuer 'Enabled'/'Disabled' are frontend-synthesized labels (mapped
|
|
// from the `enabled bool` field on the Issuer struct), not Go-emitted
|
|
// enum values, but they're surfaced via StatusBadge for consistency.
|
|
const statusStyles: Record<string, string> = {
|
|
// Certificate statuses (internal/domain/certificate.go::CertificateStatus*)
|
|
Active: 'badge-success',
|
|
Expiring: 'badge-warning',
|
|
Expired: 'badge-danger',
|
|
RenewalInProgress: 'badge-info',
|
|
Archived: 'badge-neutral',
|
|
Revoked: 'badge-danger',
|
|
// Job statuses (internal/domain/job.go::JobStatus*) — note: 'Pending' is
|
|
// shared between CertificateStatusPending and JobStatusPending.
|
|
Pending: 'badge-info',
|
|
AwaitingCSR: 'badge-info',
|
|
AwaitingApproval: 'badge-info',
|
|
Running: 'badge-warning',
|
|
Completed: 'badge-success',
|
|
Failed: 'badge-danger',
|
|
Cancelled: 'badge-neutral',
|
|
// Agent statuses (internal/domain/connector.go::AgentStatus*) — D-1:
|
|
// 'Degraded' replaces the never-emitted 'Stale' from pre-D-1 (the Go
|
|
// domain has only Online / Offline / Degraded; mapping 'Stale' yellow
|
|
// and letting 'Degraded' fall through to neutral hid degraded agents).
|
|
Online: 'badge-success',
|
|
Offline: 'badge-danger',
|
|
Degraded: 'badge-warning',
|
|
// Discovery statuses (internal/domain/discovery.go::DiscoveryStatus*)
|
|
Unmanaged: 'badge-warning',
|
|
Managed: 'badge-success',
|
|
Dismissed: 'badge-neutral',
|
|
// Issuer statuses (frontend-synthesized from Issuer.enabled bool)
|
|
Enabled: 'badge-success',
|
|
Disabled: 'badge-neutral',
|
|
// Notification statuses (internal/domain/notification.go::NotificationStatus*)
|
|
// — D-2: added 'dead' (retries exhausted, dead-letter queue). Pre-D-2 it
|
|
// fell through to neutral, visually equating "needs operator attention"
|
|
// with "operator already acknowledged" (read).
|
|
sent: 'badge-success',
|
|
pending: 'badge-warning',
|
|
failed: 'badge-danger',
|
|
dead: 'badge-danger',
|
|
read: 'badge-neutral',
|
|
// Health check statuses (internal/domain/health_check.go::HealthStatus*)
|
|
healthy: 'badge-success',
|
|
degraded: 'badge-warning',
|
|
down: 'badge-danger',
|
|
cert_mismatch: 'badge-warning',
|
|
unknown: 'badge-neutral',
|
|
};
|
|
|
|
export default function StatusBadge({ status }: { status: string }) {
|
|
const cls = statusStyles[status] || 'badge-neutral';
|
|
return <span className={`badge ${cls}`}>{status}</span>;
|
|
}
|