Task · SKT-0010.02

Record real instrument types in both corpus producers

Description

Every metric entry in both committed corpora carries instrument_types: ["unknown"], which is why SKT-0010.01 has to treat that value as absent evidence. That is the fail-safe; this is the actual fix. The instrument type is the single field most likely to catch a real defect (counter emitted as a gauge, _sum treated as a rate), so leaving it permanently unobserved would gut the audit.

Both producers can do better than the sentinel:

Where a producer genuinely cannot observe the type, say so in the corpus source block. Do not synthesise a type from the metric name suffix: inferring counter from _total would launder a guess into the ground truth, and the corpus is the only ground truth in the repository.

Acceptance Criteria

Definition of Done

Implementation Notes

DELIVERED 2026-08-27 (lane L2). The sentinel went from universal to the exception: typed coverage 12/676 -> 513/706, and no type anywhere is derived from a metric name.

Mechanisms

gcx read-back: the stack Prometheus metadata API, read ONCE per run and looked up by EXACT metric name only, mapped through a fixed table onto the instrument vocabulary. A miss keeps the sentinel.

k3d lab: two mechanisms, neither a name. (1) prompb.WriteRequest.metadata field 3 decoded at the capture receiver, with field numbers verified against the prompb protos in the pinned Prometheus 3.12.0 module rather than from memory. (2) The reserved label names of the exposition contract — Prometheus RESERVES le for classic histogram buckets and quantile for summary quantiles, so a series carrying one IS that instrument; a native histogram sample counts equally.

ROOT DECISION on the second mechanism: KEEP it. The lane flagged it as a possible AC #3 violation and offered to delete it. Reserved label names carry normative force where a name suffix does not — the AC prohibition targets deriving type from a metric NAME (_total implies counter), which is a naming convention. Without this mechanism the k3d producer contributes zero type evidence at all.

THE HEADLINE VALIDATION RESULT

ZERO synth-vs-reality instrument contradictions. All 596 instrument_mismatch findings were the sentinel artefact. Every one of the 200 overlapping CloudWatch families AGREES with the live stack — including all 101 _sum families and every _sample_count reading as gauge. So AGENTS.md rule that a CloudWatch _sum is a per-period GAUGE and never a rate is now LIVE-VERIFIED rather than asserted.

Report effect: 1806 -> 883 findings; instrument_mismatch 596 -> 103, and all 103 survivors are reality=[unknown], the absent-evidence class.

Live-verified facts that cost real calls

A corpus contradiction found and corrected

storage_operation_duration_seconds carried instrument_types: [“histogram”] purely from its _sum/_count suffix, with EMPTY bucket_bounds proving no bucket series was ever captured. Now unknown. The claim happens to be true of the real kubelet, but the corpus had no evidence for it, and a ground-truth corpus must not carry a lucky guess.

Two latent bugs fixed in cmd/reality-corpus-gcx

Both would have broken any agent-run refresh, neither related to instrument types. (1) execute used CombinedOutput while gcx writes an advisory hint to STDERR when it detects an agent harness — that hint fed straight into a DisallowUnknownFields decoder. (2) gcx version emits JSON not a table under the same detection, and the parser died on it.

A receiver defect fixed

RW2 probing returned zero receipts. Cause: the receiver never sent the X-Prometheus-Remote-Write-*-Written response headers that the Remote-Write 2.0 spec makes MANDATORY, so a compliant sender reads their absence as nothing having been written. Fixed with header names taken from the spec. Re-probing RW2 needs a lab run AND a decision not taken: switching the lab pinned protocol changes what the k3d corpus is evidence of, since the chart real default is v1. The values file is restored to v1.

Docker was used for three full k3d lab runs and the corpus was regenerated. The refresh is a genuine cumulative union — 618 CW families = 588 committed + 30 newly observed, with ten firehose families absent from the new window PRESERVED.

Still open: the InstrumentTypeSource corpus field and the merge-identity problem, both tracked as SKT-0010.10 with the exact values to stamp.

2026-09-06 disposition, case (a): inspected every current corpus document carrying unknown instrument types. Original read-back and lab source blocks now contain explicit instrument_type_source reasons. The seven capture-v2 source blocks carry METADATA_ABSENT_FOR_SCRAPED_METRICS and UNTYPED_FAMILIES_ARE_A_SENDER_PROPERTY warnings, and the unknown family entries record instrument_type_source=undetermined. RKSY-0027 documents the measured ingest-path distinction. Source-block absence reasons are now present; no capture was changed.

Final Summary

Both corpus producers now read a real instrument type from evidence: typed coverage went from 12 of 676 to 513 of 706, and no type anywhere is derived from a metric name.

The headline is a validation result rather than a mechanism. ZERO synth-vs-reality instrument contradictions — all 596 instrument_mismatch findings were the sentinel artefact, and every one of the 200 overlapping CloudWatch families AGREES with the live stack, including all 101 _sum families reading as gauge. The AGENTS.md rule that a CloudWatch _sum is a per-period gauge and never a rate is now live-verified rather than asserted.

Three mechanisms, none a name. The gcx read-back uses the stack Prometheus metadata API, looked up by exact metric name. The k3d lab decodes remote-write metadata, and separately uses the reserved label names of the exposition contract — Prometheus RESERVES le and quantile, so a series carrying one IS that instrument. The root kept that second mechanism deliberately: reserved label names carry normative force where a _total suffix is only a convention, and without it the k3d producer contributes no type evidence at all.

Three facts that cost real calls: the metadata API answers for the CloudWatch scraper path but returns NOTHING for the remote-write path; its answer is a time-windowed snapshot, not a catalogue, with two reads twenty minutes apart differing by 34 names dropped and 21 added; and k8s-monitoring 4.4.0 sends ZERO remote-write v1 metadata across 12,651 decoded series.

A corpus contradiction was also found and corrected: storage_operation_duration_seconds was typed histogram purely from its suffix with empty bucket bounds. The claim is true of the real kubelet, but the corpus had no evidence for it, and a ground-truth corpus must not carry a lucky guess.

AC #4 is unchecked: it needs a CorpusSource field that did not exist and could not be added from this lane. The exact field, validation, and the four final provenance strings are recorded here and tracked as SKT-0010.10, which also carries the merge-identity data-loss trap the refresh exposed. Two latent bugs in the read-back CLI and a mandatory-response-header defect in the capture receiver were fixed along the way. make gate green.

2026-09-06 reconciliation supersedes the earlier unchecked AC4 statement and deferred ownership: source-block reasons are now present and AC4 is checked against the current corpus.

View the source file on GitHub