gastownhall/beads · error

provenance: unknown kind %q

Error message

provenance: unknown kind %q

What it means

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.

Source

Thrown at internal/storage/issueops/provenance.go:55

}

// ReservedProvSource is reserved for derived/reconstructed events so a consumer's
// 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")
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use the exported types.Prov* constants instead of hand-written strings for ev.Kind
  2. Check the accepted set (see knownProvKinds in internal/storage/issueops/provenance.go) and map your event to the nearest supported kind
  3. If a new kind is genuinely needed, propose it upstream rather than bypassing validation
  4. Pre-validate with ValidateProvenanceEvent before opening a transaction so failures are cheap

Example fix

// before
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvKind("deployed"), Source: "ci"}
// after
ev := types.ProvenanceEvent{IssueID: id, Kind: types.ProvLand, Source: "ci"} // use a known Prov* constant
Defensive patterns

Strategy: validation

Validate before calling

validKinds := map[types.ProvKind]bool{
	types.ProvCut: true, types.ProvClaim: true, types.ProvSuspend: true, types.ProvResume: true,
	types.ProvHandoff: true, types.ProvCommit: true, types.ProvLand: true, types.ProvUsed: true,
}
if !validKinds[ev.Kind] { return fmt.Errorf("unsupported provenance kind %q", ev.Kind) }

Type guard

func isKnownProvKind(k types.ProvKind) bool {
	switch k {
	case types.ProvCut, types.ProvClaim, types.ProvSuspend, types.ProvResume,
		types.ProvHandoff, types.ProvCommit, types.ProvLand, types.ProvUsed:
		return true
	}
	return false
}

Try / catch

if err := issueops.ValidateProvenanceEvent(ev); err != nil {
	var kind types.ProvKind
	if strings.Contains(err.Error(), "unknown kind") {
		_ = kind // map or drop the event; do not pass it to the store
		return fmt.Errorf("unsupported provenance kind %q", ev.Kind)
	}
	return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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