gastownhall/beads · error
provenance: source %q is reserved for ingest backfill and ca
Error message
provenance: source %q is reserved for ingest backfill and cannot be recorded directly
What it means
Certain source values are reserved for system use: ReservedProvSource (the ingest backfill marker) cannot be attached to directly recorded events, so consumers can use it as an honesty filter to exclude derived/reconstructed rows. ValidateProvenanceEvent rejects any event whose Source equals this reserved value (case-insensitively).
Source
Thrown at internal/storage/issueops/provenance.go:61
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,
// two distinct events would collapse to the same content-addressed id. Guard
// at the store boundary so every caller (CLI or library) is covered.
if (ev.Ref == nil || *ev.Ref == "") && ev.OccurredAt == nil {View on GitHub (pinned to 71377f2769)
Solutions
- Choose a distinct, non-reserved Source value for your events (e.g. "import:mytool")
- Use the dedicated ingest/backfill path for derived or reconstructed events instead of RecordProvenanceEventInTx
- Normalize your source strings and compare against ReservedProvSource before recording
- On import, rewrite reserved sources from the source export to your own namespace
Example fix
// before
ev.Source = "ingest-backfill" // reserved
_ = issueops.RecordProvenanceEventInTx(ctx, tx, ev)
// after
ev.Source = "import:mytool"
if strings.EqualFold(strings.TrimSpace(ev.Source), issueops.ReservedProvSource) {
return fmt.Errorf("source %q is reserved; pick another", ev.Source)
}
return issueops.RecordProvenanceEventInTx(ctx, tx, ev) Defensive patterns
Strategy: validation
Validate before calling
if strings.EqualFold(strings.TrimSpace(ev.Source), issueops.ReservedProvSource) {
return fmt.Errorf("source %q is reserved for ingest backfill", ev.Source)
}
return issueops.ValidateProvenanceEvent(ev) Type guard
func isReservedSource(s string) bool { return strings.EqualFold(strings.TrimSpace(s), issueops.ReservedProvSource) } Try / catch
if err := issueops.RecordProvenanceEventInTx(ctx, tx, ev); err != nil {
if strings.Contains(err.Error(), "is reserved") {
return fmt.Errorf("rewrite source %q to your own namespace before recording", ev.Source)
}
return err
} Prevention
- Namespace your sources (e.g. "import:mytool") instead of reusing system values
- Compare imports against issueops.ReservedProvSource (case-insensitively) and rewrite on conflict
- Route derived/reconstructed events through the ingest backfill path, not the record path
- Treat the reserved source as an integrity marker, never as a label for your own events
When it happens
Trigger: Calling RecordProvenanceEventInTx with ev.Source set (in any casing) to the value of issueops.ReservedProvSource; importing/backfilling events by writing them through the normal record path instead of the ingest backfill path.
Common situations: Import scripts copying exported events verbatim (including backfill rows) and re-recording them; tooling that marks its own events as 'backfill' or similar to seem system-generated; reconstruction tools writing derived events through the wrong API.
Related errors
- provenance: issue id is required
- provenance: unknown kind %q
- provenance: source is required
- 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/41588bf2affb8149.
Report an issue: GitHub.