mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:51:30 +00:00
fix(web): close StatusBadge enum drift + Certificate TS phantom fields (D-1 master)
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.
This commit is contained in:
@@ -270,6 +270,80 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Forbidden StatusBadge dead-key + Certificate phantom-field regression guard (D-1)
|
||||
# D-1 master closed cat-d-359e92c20cbf (Agent: 'Stale' dead key,
|
||||
# 'Degraded' missing), cat-d-9f4c8e4a91f1 (Notification: 'dead'
|
||||
# missing), cat-d-1447e04732e7 (Cert: 'PendingIssuance' dead
|
||||
# key), cat-f-cert_detail_page_key_render_fallback (render-site
|
||||
# uses cert.X directly), and cat-f-ae0d06b6588f (Certificate
|
||||
# TS phantom fields). This step grep-fails the build if either
|
||||
# half of the closure is reverted:
|
||||
#
|
||||
# 1. The dead StatusBadge keys ('Stale' for Agent, 'PendingIssuance'
|
||||
# for Cert) reappearing as map literals, OR
|
||||
# 2. The five phantom Certificate TS fields (serial_number,
|
||||
# fingerprint_sha256, key_algorithm, key_size, issued_at)
|
||||
# reappearing on the `Certificate` interface in types.ts
|
||||
# (CertificateVersion legitimately carries them and is
|
||||
# explicitly excluded by the awk pre-filter below).
|
||||
#
|
||||
# Comments are exempt so the closure prose in StatusBadge.tsx +
|
||||
# types.ts can stay. Test files are exempt so negative tests
|
||||
# asserting the dead keys fall through to neutral keep working.
|
||||
#
|
||||
# See coverage-gap-audit-2026-04-24-v5/unified-audit.md
|
||||
# cat-d-* / cat-f-* for the closure rationale, or
|
||||
# web/src/components/StatusBadge.test.tsx for the live
|
||||
# enum-coverage contract.
|
||||
run: |
|
||||
set -e
|
||||
|
||||
BAD_BADGE=$(grep -nE "^\s*(Stale|PendingIssuance)\s*:\s*'badge-" \
|
||||
web/src/components/StatusBadge.tsx 2>/dev/null \
|
||||
| grep -v '\.test\.' \
|
||||
| grep -vE '^\s*[^:]+:[0-9]+:\s*//' \
|
||||
|| true)
|
||||
if [ -n "$BAD_BADGE" ]; then
|
||||
echo "D-1 regression: dead StatusBadge key reappeared:"
|
||||
echo "$BAD_BADGE"
|
||||
echo ""
|
||||
echo "Allowed surface: comment lines naming the removed key in"
|
||||
echo "the file's preamble. The Go-side AgentStatus values are"
|
||||
echo "Online/Offline/Degraded (no Stale); CertificateStatus values"
|
||||
echo "are Pending/Active/... (no PendingIssuance). See"
|
||||
echo "web/src/components/StatusBadge.test.tsx for the contract."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Certificate TS phantom-field check. Scoped to the
|
||||
# `export interface Certificate {` block in web/src/api/types.ts
|
||||
# — CertificateVersion legitimately declares these fields and
|
||||
# must NOT trip the guardrail. The awk window opens on the
|
||||
# exact `Certificate {` header (not `CertificateVersion {`,
|
||||
# not `CertificateProfile {`) and closes at the first `}`,
|
||||
# then the grep matches a phantom-field declaration anywhere
|
||||
# in that window.
|
||||
BAD_TS=$(awk '
|
||||
/^export interface Certificate \{/ { flag=1; next }
|
||||
flag && /^\}/ { flag=0 }
|
||||
flag { print FILENAME":"NR":"$0 }
|
||||
' web/src/api/types.ts \
|
||||
| grep -E '\b(serial_number|fingerprint_sha256|key_algorithm|key_size|issued_at)\??\s*:' \
|
||||
|| true)
|
||||
if [ -n "$BAD_TS" ]; then
|
||||
echo "D-1 regression: Certificate TS interface re-added a phantom field:"
|
||||
echo "$BAD_TS"
|
||||
echo ""
|
||||
echo "These fields live on CertificateVersion, not ManagedCertificate."
|
||||
echo "The Go-side ManagedCertificate has never carried them; the"
|
||||
echo "TS optional declarations were silently undefined on every"
|
||||
echo "list response. Render-site consumers (e.g. CertificateDetailPage)"
|
||||
echo "use latestVersion?.field as the canonical access path."
|
||||
echo "See coverage-gap-audit-2026-04-24-v5/unified-audit.md"
|
||||
echo "cat-f-ae0d06b6588f for the closure rationale."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Race Detection
|
||||
run: go test -race ./internal/service/... ./internal/api/handler/... ./internal/api/middleware/... ./internal/scheduler/... ./internal/connector/... ./internal/crypto/... ./internal/domain/... ./internal/validation/... ./internal/tlsprobe/... -count=1 -timeout 300s
|
||||
|
||||
|
||||
Reference in New Issue
Block a user