{"record":{"id":"59a5f0e4e1467299","repo":"gastownhall/beads","slug":"provenance-unknown-ref-kind-q","errorCode":null,"errorMessage":"provenance: unknown ref-kind %q","messagePattern":"provenance: unknown ref-kind %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/provenance.go","lineNumber":65,"sourceCode":"// before it is recorded: kind, ref_kind (when present), the git-sha ref shape,\n// and the reserved source. It never interprets the opaque actor/ref values. It\n// is exported so the CLI can fail early with the same rules the store enforces.\nfunc 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}","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/provenance.go#L47-L83","documentation":"If an event carries a RefKind, it must be one of the known ref kinds (git-sha, pr, work-id, transcript, branch); an unrecognized RefKind is rejected with 'provenance: unknown ref-kind %q'. RefKind is a closed vocabulary so downstream tools know how to resolve refs.","triggerScenarios":"Recording an event with ev.RefKind set to anything outside {\"git-sha\",\"pr\",\"work-id\",\"transcript\",\"branch\"} — e.g. \"commit\", \"sha\", \"url\", or the empty string in a non-nil pointer.","commonSituations":"Mapping external systems' link types (commit, issue URL) directly onto RefKind without translating to the supported set; typo'd ref kinds in scripts; nil-vs-empty confusion producing RefKind=\"\".","solutions":["Translate external link types to the supported set: commit SHA -> \"git-sha\", PR number -> \"pr\", etc.","Only set ev.RefKind when you actually have a ref; leave it nil otherwise (note the next check also demands a non-empty Ref for any set RefKind)","Use string constants mirroring knownProvRefKinds rather than inline literals","Pre-validate with ValidateProvenanceEvent before opening the transaction"],"exampleFix":"// before\nevk := \"commit\"\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvCommit, Source: \"ci\", RefKind: &evk, Ref: &sha}\n// after\nevk := \"git-sha\" // must be one of: git-sha, pr, work-id, transcript, branch\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvCommit, Source: \"ci\", RefKind: &evk, Ref: &sha}\nif err := issueops.ValidateProvenanceEvent(ev); err != nil {\n\treturn err\n}","handlingStrategy":"validation","validationCode":"validRefKinds := map[string]bool{\"git-sha\": true, \"pr\": true, \"work-id\": true, \"transcript\": true, \"branch\": true}\nif ev.RefKind != nil && !validRefKinds[*ev.RefKind] {\n\treturn fmt.Errorf(\"unsupported ref-kind %q\", *ev.RefKind)\n}\nreturn issueops.ValidateProvenanceEvent(ev)","typeGuard":"func isKnownRefKind(k *string) bool {\n\tif k == nil { return true }\n\tswitch *k {\n\tcase \"git-sha\", \"pr\", \"work-id\", \"transcript\", \"branch\": return true\n\t}\n\treturn false\n}","tryCatchPattern":"if err := issueops.ValidateProvenanceEvent(ev); err != nil {\n\tif strings.Contains(err.Error(), \"unknown ref-kind\") {\n\t\treturn fmt.Errorf(\"translate ref-kind %q to git-sha/pr/work-id/transcript/branch\", *ev.RefKind)\n\t}\n\treturn err\n}","preventionTips":["Define string constants for the five valid ref kinds and use them everywhere","Translate external link types at your integration boundary (commit -> git-sha)","Leave RefKind nil when there is no ref — never set an empty-string kind","Remember ref-kind \"git-sha\" additionally requires a value matching the git SHA pattern; pre-validate that too"],"tags":["go","validation","provenance","enum"],"backgroundTag":"provenance-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}