{"record":{"id":"7b022dbf51390bf0","repo":"gastownhall/beads","slug":"provenance-ref-kind-q-requires-a-ref","errorCode":null,"errorMessage":"provenance: ref-kind %q requires a ref","messagePattern":"provenance: ref-kind %q requires a ref","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/provenance.go","lineNumber":68,"sourceCode":"func ValidateProvenanceEvent(ev types.ProvenanceEvent) error {\n\tif strings.TrimSpace(ev.IssueID) == \"\" {\n\t\treturn fmt.Errorf(\"provenance: issue id is required\")\n\t}\n\tif _, ok := knownProvKinds[ev.Kind]; !ok {\n\t\treturn fmt.Errorf(\"provenance: unknown kind %q\", ev.Kind)\n\t}\n\tif strings.TrimSpace(ev.Source) == \"\" {\n\t\treturn fmt.Errorf(\"provenance: source is required\")\n\t}\n\tif strings.EqualFold(strings.TrimSpace(ev.Source), ReservedProvSource) {\n\t\treturn fmt.Errorf(\"provenance: source %q is reserved for ingest backfill and cannot be recorded directly\", ReservedProvSource)\n\t}\n\tif ev.RefKind != nil {\n\t\tif _, ok := knownProvRefKinds[*ev.RefKind]; !ok {\n\t\t\treturn fmt.Errorf(\"provenance: unknown ref-kind %q\", *ev.RefKind)\n\t\t}\n\t\tif ev.Ref == nil || *ev.Ref == \"\" {\n\t\t\treturn fmt.Errorf(\"provenance: ref-kind %q requires a ref\", *ev.RefKind)\n\t\t}\n\t\tif *ev.RefKind == \"git-sha\" {\n\t\t\tif !gitSHARE.MatchString(*ev.Ref) {\n\t\t\t\treturn fmt.Errorf(\"provenance: ref-kind git-sha requires a 40-character lowercase hex ref\")\n\t\t\t}\n\t\t}\n\t}\n\t// A ref-less event is keyed by occurred_at for its stable id; without either,\n\t// two distinct events would collapse to the same content-addressed id. Guard\n\t// at the store boundary so every caller (CLI or library) is covered.\n\tif (ev.Ref == nil || *ev.Ref == \"\") && ev.OccurredAt == nil {\n\t\treturn fmt.Errorf(\"provenance: event with no ref requires occurred_at (--at) for a stable id\")\n\t}\n\treturn nil\n}\n\n// ProvenanceEventID computes the deterministic, idempotent id for a provenance\n// event from source:issue:kind:(ref or occurred_at). A producer firing twice","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/provenance.go#L50-L86","documentation":"ValidateProvenanceEvent rejects a provenance event whose RefKind is a known kind but whose Ref pointer is nil or empty. Provenance events are content-addressed by source:issue:kind:(ref or occurred_at); a declared ref-kind without an actual ref value would produce an event that cannot be keyed or traced. The store refuses such events at the boundary so all callers (CLI or library) get a consistent error.","triggerScenarios":"Calling RecordProvenanceEventInTx (which runs ValidateProvenanceEvent) with an event that sets RefKind to a known kind (e.g. \"branch\", \"git-sha\") but leaves Ref nil or sets it to \"\".","commonSituations":"Building a ProvenanceEvent struct programmatically and populating RefKind from config while forgetting to thread the actual git ref/branch value through; passing a pointer to an empty string because the ref was resolved too late or an env lookup failed silently.","solutions":["Set ev.Ref to a non-empty string (pointer to the ref value) whenever ev.RefKind is set","If no ref is available, either pass the event timestamp via OccurredAt and leave both RefKind and Ref nil, or skip recording the event","Add a caller-side check that RefKind and Ref are set together before invoking the store"],"exampleFix":"// before\nev := ProvenanceEvent{RefKind: strPtr(\"branch\")}\n// after\nref := \"feature/fix-xyz\"\nev := ProvenanceEvent{RefKind: strPtr(\"branch\"), Ref: &ref}","handlingStrategy":"validation","validationCode":"func validProvRef(ev ProvenanceEvent) bool {\n  return ev.RefKind == nil || (ev.Ref != nil && *ev.Ref != \"\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set RefKind and Ref together via a constructor helper","Never pass a pointer to an empty string as Ref","Treat 'unknown ref-kind' vs 'requires a ref' errors as separate signal: first fix the kind, then the value"],"tags":["provenance","validation","storage"],"backgroundTag":"provenance-ref-required","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}