fix: validate schema version on read; guard zero user id in pipeline indexing
Problem
Two cross-stage data-integrity gaps:
- Schema version drift, unvalidated on read. The collector wrote
Metadata.SchemaVersion = "1.0"while the analyzer and testdata wrote"1.0.0". Nothing validated the version when the analyzer read collector output, so a stale or incompatible artifact silently mis-parsed to zero values instead of failing loud. - Ghost user-0 in teams. A pipeline with no attributable user carries
UserID: 0. That is harmless in per-user scoring because user 0 is never in the roster, butinternal/analyzer/teams.go(userPrimaryProjects) rangesPipelinesByUserdirectly. AUserID:0pipeline therefore seeded a ghost "user 0" team member that inflated member counts and dragged team averages.
Fix
Schema version: canonicalize plus validate on read.
- Add
schema.CurrentSchemaVersion = "1.0.0"andschema.IsSupportedSchemaVersion(v)tointernal/schema/metadata.go. The helper accepts the canonical version and the legacy"1.0"so existing artifacts still load during the transition. - Every write site stamps
schema.CurrentSchemaVersion: collector (internal/collector/collector.go), analyzer (internal/analyzer/analyze.go), testdata (internal/testdata/generate.go). - Validate on read with a clear error: the analyzer load path
loadCollectedData(internal/analyzer/analyze.go) and the collector incremental read pathloadEnvelopeData(internal/collector/incremental.go) reject an unsupported version (artifact schema version %q unsupported by this manifold build; re-run collect) instead of proceeding. - Watermark (
internal/collector/watermark.go) carries its ownWatermarkVersion, not a schema version, and already validates non-empty. Left unchanged.
Guard UserID == 0 in pipeline indexing.
- Add a
p.UserID != 0guard wherePipelinesByUseris built ininternal/analyzer/scorer.go, mirroring the existing!= 0guards onMRsByMergeUserandIssuesByCloserin the same constructor.
Validation
make check (vet plus test) and go test -race ./... pass on Go 1.24.2. No data races.
Fails-on-old / passes-on-fix proofs (temporary test, reverted each fix in turn):
- Schema read. With validation in place: an artifact stamped
"9.9"is rejected with the clear error,"1.0.0"loads, legacy"1.0"loads. With the read validation reverted: the"9.9"artifact loads silently with no error. Restored: rejected again. - User-0 guard. With the guard in place: a
PipelinesByUserbuilt from a pipeline set including aUserID:0pipeline has no key0(keys present:[1]). With the guard reverted: key0appears. Restored: absent again.