Task · SKT-0007.03

Guard the scrape-only OTel verdicts with an architecture test

Description

SKT-0007.01 recorded in signals/otlp-native-verdicts.md that 24 catalog kinds are Prometheus-scrape-only and must never declare core.OTLPMetrics. Nothing enforces that today: a contributor can add an OTLP lane to karpenter or ksm_ingress and no gate objects, and the result is fabricated telemetry no real deployment produces.

Create this BEFORE any emission wave, so every later wave lands against a live guard rather than against a document nobody re-reads. It is also the cheapest item in the epic.

The guard must fail in both directions. A scrape-only kind gaining an OTLP lane is the obvious case. The subtler and more valuable one: a kind registered in the catalog that appears in neither the allowed nor the forbidden list has no recorded verdict, and the test must fail on it — otherwise the verdict record silently goes stale as the catalog grows, which is exactly how the study becomes advisory again.

internal/archtest/arch_test.go is a single-owner wiring file.

Acceptance Criteria

Definition of Done

Final Summary

internal/archtest/otlp_verdict_test.go turns the 45-kind verdict study into a build failure rather than a document nobody re-reads.

It enumerates runner.Catalog() — the same composition-root wiring the production binary uses — so the count is live from the registry rather than hardcoded, and resolves each kind SOURCE DIRECTORY by reflecting on its registered Build function compiled location rather than guessing a package path from the snake_case kind name. That detail matters: k8s_cluster lives in k8scluster and csp_azure in cspazure, so a hand-written name-to-directory table would have needed its own maintenance and its own way of going stale.

Two tests. The first fails if any registered kind is missing from all three verdict buckets, AND if a bucket names a kind that is not registered — staleness in both directions. That is the more valuable half: without it the record silently rots as the catalog grows, which is exactly the failure this guard exists to prevent. The second fails if a scrape-only or unresolved kind references core.OTLPMetrics in its own non-test source.

WIRING-PASS CORRECTION worth recording, because it would have bitten the first SKT-0007 emission wave: the lane implemented the second check as a REGEX over raw source, which also matches the constant inside a COMMENT. So a scrape-only construct documenting its own constraint — “must never declare core.OTLPMetrics” — would have failed the guard. The guard punishing the documentation of its own rule, and SKT-0007 later waves are likely to write exactly that comment. Replaced with a go/parser AST walk for the core.OTLPMetrics selector expression, which is immune by construction since comments are not expressions.

All three directions proven by mutation with verbatim failure output: a real declaration fails; a comment mentioning the constant passes; a kind removed from its bucket fails, and the same run also catches the now-stale bucket entry. Counts verified against the record: 20 native, 24 scrape-only, 1 unresolved, 45 total.

The lane raised no disagreement with any recorded verdict, and copied the split mechanically rather than re-deriving it — which was the instruction.

View the source file on GitHub