{"record":{"id":"e9ce057d1fe1835d","repo":"gastownhall/beads","slug":"provenance-unknown-kind-q","errorCode":null,"errorMessage":"provenance: unknown kind %q","messagePattern":"provenance: unknown kind %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/provenance.go","lineNumber":55,"sourceCode":"}\n\n// ReservedProvSource is reserved for derived/reconstructed events so a consumer's\n// read-first honesty filter can exclude backfilled rows. The record path rejects\n// it (case-insensitively): real producers must name their own source.\nconst ReservedProvSource = \"ingest-backfill\"\n\nvar gitSHARE = regexp.MustCompile(`^[0-9a-f]{40}$`)\n\n// ValidateProvenanceEvent checks the structural fields of a provenance event\n// 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}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/provenance.go#L37-L73","documentation":"ValidateProvenanceEvent checks ev.Kind against knownProvKinds, the closed set of valid provenance kinds (cut, claim, suspend, resume, handoff, commit, land, used). An unrecognized Kind is rejected with 'provenance: unknown kind %q'. This keeps the provenance log to a known vocabulary that downstream tooling can interpret.","triggerScenarios":"Recording an event whose ev.Kind is not one of types.ProvCut/ProvClaim/ProvSuspend/ProvResume/ProvHandoff/ProvCommit/ProvLand/ProvUsed — e.g. the zero value of ProvKind, a misspelled kind, or a kind string invented by an external tool.","commonSituations":"Constructing types.ProvenanceEvent with a raw string cast instead of the typed constants; upgrading beads and encountering kinds from newer/older schemas; automation emitting ad-hoc event kinds.","solutions":["Use the exported types.Prov* constants instead of hand-written strings for ev.Kind","Check the accepted set (see knownProvKinds in internal/storage/issueops/provenance.go) and map your event to the nearest supported kind","If a new kind is genuinely needed, propose it upstream rather than bypassing validation","Pre-validate with ValidateProvenanceEvent before opening a transaction so failures are cheap"],"exampleFix":"// before\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvKind(\"deployed\"), Source: \"ci\"}\n// after\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvLand, Source: \"ci\"} // use a known Prov* constant","handlingStrategy":"validation","validationCode":"validKinds := map[types.ProvKind]bool{\n\ttypes.ProvCut: true, types.ProvClaim: true, types.ProvSuspend: true, types.ProvResume: true,\n\ttypes.ProvHandoff: true, types.ProvCommit: true, types.ProvLand: true, types.ProvUsed: true,\n}\nif !validKinds[ev.Kind] { return fmt.Errorf(\"unsupported provenance kind %q\", ev.Kind) }","typeGuard":"func isKnownProvKind(k types.ProvKind) bool {\n\tswitch k {\n\tcase types.ProvCut, types.ProvClaim, types.ProvSuspend, types.ProvResume,\n\t\ttypes.ProvHandoff, types.ProvCommit, types.ProvLand, types.ProvUsed:\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"if err := issueops.ValidateProvenanceEvent(ev); err != nil {\n\tvar kind types.ProvKind\n\tif strings.Contains(err.Error(), \"unknown kind\") {\n\t\t_ = kind // map or drop the event; do not pass it to the store\n\t\treturn fmt.Errorf(\"unsupported provenance kind %q\", ev.Kind)\n\t}\n\treturn err\n}","preventionTips":["Always use the exported types.Prov* constants; never cast raw strings to ProvKind","Map external event vocabularies to the supported kinds at the boundary","Keep a mapping table for tool-specific events (e.g. deploy -> ProvLand)","Re-check kinds after beads upgrades in case the closed set changes"],"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"}