gastownhall/beads · error
provenance: source is required
Error message
provenance: source is required
What it means
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.
Source
Thrown at internal/storage/issueops/provenance.go:58
// read-first honesty filter can exclude backfilled rows. The record path rejects
// it (case-insensitively): real producers must name their own source.
const ReservedProvSource = "ingest-backfill"
var gitSHARE = regexp.MustCompile(`^[0-9a-f]{40}$`)
// ValidateProvenanceEvent checks the structural fields of a provenance event
// before it is recorded: kind, ref_kind (when present), the git-sha ref shape,
// and the reserved source. It never interprets the opaque actor/ref values. It
// is exported so the CLI can fail early with the same rules the store enforces.
func ValidateProvenanceEvent(ev types.ProvenanceEvent) error {
if strings.TrimSpace(ev.IssueID) == "" {
return fmt.Errorf("provenance: issue id is required")
}
if _, ok := knownProvKinds[ev.Kind]; !ok {
return fmt.Errorf("provenance: unknown kind %q", ev.Kind)
}
if strings.TrimSpace(ev.Source) == "" {
return fmt.Errorf("provenance: source is required")
}
if strings.EqualFold(strings.TrimSpace(ev.Source), ReservedProvSource) {
return fmt.Errorf("provenance: source %q is reserved for ingest backfill and cannot be recorded directly", ReservedProvSource)
}
if ev.RefKind != nil {
if _, ok := knownProvRefKinds[*ev.RefKind]; !ok {
return fmt.Errorf("provenance: unknown ref-kind %q", *ev.RefKind)
}
if ev.Ref == nil || *ev.Ref == "" {
return fmt.Errorf("provenance: ref-kind %q requires a ref", *ev.RefKind)
}
if *ev.RefKind == "git-sha" {
if !gitSHARE.MatchString(*ev.Ref) {
return fmt.Errorf("provenance: ref-kind git-sha requires a 40-character lowercase hex ref")
}
}
}
// A ref-less event is keyed by occurred_at for its stable id; without either,View on GitHub (pinned to 71377f2769)
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
Example fix
// before
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvClaim, Actor: "alice"}
// after
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvClaim, Actor: "alice", Source: "agent:alice"} Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(ev.Source) == "" {
return errors.New("provenance event requires a source")
}
return issueops.ValidateProvenanceEvent(ev) Type guard
func hasSource(ev types.ProvenanceEvent) bool { return strings.TrimSpace(ev.Source) != "" } Try / catch
if err := issueops.RecordProvenanceEventInTx(ctx, tx, ev); err != nil {
if strings.Contains(err.Error(), "source is required") {
return fmt.Errorf("provenance event from %q missing source", ev.Actor)
}
return err
} Prevention
- 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
When it happens
Trigger: Recording a provenance event with ev.Source == "" or " " via RecordProvenanceEventInTx or a direct ValidateProvenanceEvent call.
Common situations: 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.
Related errors
- provenance: issue id is required
- provenance: unknown kind %q
- provenance: source %q is reserved for ingest backfill and ca
- provenance: unknown ref-kind %q
- no store is open for this workspace
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/673c9c0a2a39f1b2.
Report an issue: GitHub.