{"record":{"id":"673c9c0a2a39f1b2","repo":"gastownhall/beads","slug":"provenance-source-is-required","errorCode":null,"errorMessage":"provenance: source is required","messagePattern":"provenance: source is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/provenance.go","lineNumber":58,"sourceCode":"// 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}\n\t\t}\n\t}\n\t// A ref-less event is keyed by occurred_at for its stable id; without either,","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/provenance.go#L40-L76","documentation":"Every provenance event must carry a non-empty Source identifying where it came from; ValidateProvenanceEvent rejects events whose Source is empty or whitespace-only. Source is distinct from actor/ref, which are opaque — Source is the structural 'who is reporting this' field.","triggerScenarios":"Recording a provenance event with ev.Source == \"\" or \"   \" via RecordProvenanceEventInTx or a direct ValidateProvenanceEvent call.","commonSituations":"Automation that fills actor but forgets source; refactored call sites where the source parameter was dropped; parsing events from external systems that lack a source field.","solutions":["Set ev.Source to a stable identifier for the emitting component (e.g. \"cli\", \"agent:foo\", \"ci\")","Add a construction helper that requires source, so it cannot be omitted","Pre-validate with ValidateProvenanceEvent before recording to get a precise message","Default source from context (command name / agent name) at the call site if it is genuinely unknown"],"exampleFix":"// before\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvClaim, Actor: \"alice\"}\n// after\nev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvClaim, Actor: \"alice\", Source: \"agent:alice\"}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(ev.Source) == \"\" {\n\treturn errors.New(\"provenance event requires a source\")\n}\nreturn issueops.ValidateProvenanceEvent(ev)","typeGuard":"func hasSource(ev types.ProvenanceEvent) bool { return strings.TrimSpace(ev.Source) != \"\" }","tryCatchPattern":"if err := issueops.RecordProvenanceEventInTx(ctx, tx, ev); err != nil {\n\tif strings.Contains(err.Error(), \"source is required\") {\n\t\treturn fmt.Errorf(\"provenance event from %q missing source\", ev.Actor)\n\t}\n\treturn err\n}","preventionTips":["Require source in your event-construction helper signature","Default source from execution context (CLI command name, agent ID, CI job)","Never treat actor as a substitute for source — they are distinct fields","Validate before recording so failures happen before any DB work"],"tags":["go","validation","provenance"],"backgroundTag":"provenance-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}