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

  1. Choose a distinct, non-reserved Source value for your events (e.g. "import:mytool")
  2. Use the dedicated ingest/backfill path for derived or reconstructed events instead of RecordProvenanceEventInTx
  3. Normalize your source strings and compare against ReservedProvSource before recording
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/41588bf2affb8149. Report an issue: GitHub.