Description
Found by SKT-0006.05 while building the OTLP pod-logs lane, 2026-08-27. Predates that task and affects both transports equally.
internal/inventory/synth.go keys a log entry on stream.Labels["source"]. Pod-log streams deliberately carry NO source label — SK-20 live-verified that, and reserves source for the journal and kubernetes-events lanes. So synthkit projects every pod-log entry under an empty source, while the corpus records source: k8s_pod_logs.
The consequence is that the fidelity comparator can never match pod-log entries at all, on either transport. They cannot contradict and they cannot be confirmed; they are simply invisible to the audit. That is worse than a false finding, because a silent hole reads as a clean result — and pod logs are one of the highest-traffic surfaces synthkit emits.
The fix is not to stamp a source label onto the wire: SK-20 established that no real pod-log stream carries one, and inventing it to make a comparison work would be correcting the capture to the synth. The inventory projection has to classify a log entry by its SHAPE — the label set identifies a pod-log stream unambiguously — rather than by a label that correctly does not exist.
The new OTLP transport inherits the same empty source deliberately, for the same reason.
Acceptance Criteria
- #1 Pod-log entries are classified and matched by the comparator on both transports
- #2 No source label is added to any emitted stream or resource, on either transport
- #3 The classification is by shape and is proven against both the k3d corpus entry and the synth inventory
- #4 Any divergence the newly visible pod-log entries reveal is reported, not absorbed
Definition of Done
- #1 make gate (build vet test race rw-proto-check spdx-check forbidden-words)
- #2 make blueprint-schema (only if a blueprint field or construct/workload config struct changed)
- #3 DRY_RUN=true go run ./cmd/synthkit -once -dump — inventory diffed against signals/
Final Summary
Pod-log entries are now comparable at all, on both transports, without a source label being invented anywhere.
The shape rule reads label KEYS only: a stream is a pod-log stream when it carries the full namespace/pod/container identity triple in any of the three spellings a real pipeline puts on the wire — dotted OTLP, underscore-sanitised Loki, or the classic Alloy namespace/pod/container form. Reading keys rather than values means it works identically on a live stream and on a corpus document whose values are elided.
THE DEEPER DEFECT, which the task description did not capture: shape classification alone would have changed nothing, because the comparator identity deliberately excludes the recorded source. The raw key set was BOTH the identity AND the thing being compared, so the stream-label and structured-metadata comparisons in diffLogs were unreachable dead code for logs, and two recorded shapes of one family were two families. That is the literal mechanism behind “could not contradict and could not be confirmed”. Fixing the projection without fixing the join would have left the hole open.
WHAT THE NEWLY VISIBLE ENTRIES REVEALED, which is genuinely new information:
- The OTLP transport is clean. The two captured k3d shapes are one family; once joined, keys and metadata match exactly and every reality value is elided. The single finding that existed was false and is gone.
- synthkit has a LOKI pod-log surface nobody had ever seen in the inventory. It was fused with about nine unrelated source-less lanes into a fictitious 30-key blob that could never have matched a real capture. It now resolves to a real 13-key family from the platform-addon lanes.
- TWO INCOMPATIBLE Loki pod-log shapes coexist in the catalogue: the addon lanes push the k8s_*_name spelling, while signals/k8s.md documents podLogsViaLoki as namespace/pod/container plus job and stream, which no active blueprint exercises. When SKT-0013 captures that permutation only one can match. Worth deciding BEFORE the capture lands.
- A genuine modelling gap the join absorbs and must not swallow: the real k3d cluster has pods with no Deployment owner and no node, and synthkit always sets Deployment, so it never emits that variant. The corpus schema has no optional-key concept so this cannot surface as a finding either way — it needs a cantfind PENDING or a construct change.
- A 21-key fused remainder still mixes host journal, Azure Event Hub, dbo11y and k8s objects. The lane deliberately did NOT invent shape rules for those, since there is no corpus evidence yet and naming a family without evidence is the never-invent rule.
Measured A/B on one controlled tree: 650/73/577 to 649/73/576 — exactly one line, the false extra_log, and the extra_log class disappears from the report entirely. No new findings, no regressions.