package contract import ( "fmt" "strings" ) // Identifier prefixes from FluidHypothesisRevisionSchema.md section 16. // // Implementations may use UUIDs internally; these human-readable prefixes exist // so that operational tooling and audit trails stay legible to people. const ( PrefixHypothesis = "H-" PrefixRevision = "R-" PrefixExperiment = "E-" PrefixPressure = "P-" PrefixBackendRequirement = "BR-" PrefixDecision = "D-" PrefixEvent = "EV-" PrefixFeedback = "F-" PrefixCohort = "C-" ) // EntityKind names the FLUID artifact an identifier refers to. type EntityKind string const ( KindHypothesis EntityKind = "hypothesis" KindRevision EntityKind = "revision" KindExperiment EntityKind = "experiment" KindPressure EntityKind = "pressure" KindBackendRequirement EntityKind = "backend_requirement" KindDecision EntityKind = "decision" KindEvent EntityKind = "event" KindFeedback EntityKind = "feedback" KindCohort EntityKind = "cohort" KindIntent EntityKind = "intent" KindRoutingPolicy EntityKind = "routing_policy" ) // prefixOrder matters: "BR-" must be tested before "B"-less single letters // would otherwise mis-claim it, and "EV-" before "E-". var prefixOrder = []struct { prefix string kind EntityKind }{ {PrefixBackendRequirement, KindBackendRequirement}, {PrefixEvent, KindEvent}, {PrefixHypothesis, KindHypothesis}, {PrefixRevision, KindRevision}, {PrefixExperiment, KindExperiment}, {PrefixPressure, KindPressure}, {PrefixDecision, KindDecision}, {PrefixFeedback, KindFeedback}, {PrefixCohort, KindCohort}, } // KindOf reports which FLUID artifact an identifier names. // // Audit trails carry bare identifiers across record boundaries, so being able to // classify one without knowing where it came from keeps trace reconstruction // from needing a lookup table at every hop. func KindOf(id string) (EntityKind, bool) { for _, p := range prefixOrder { if strings.HasPrefix(id, p.prefix) { return p.kind, true } } return "", false } // ErrWrongKind reports an identifier used in the wrong position. type ErrWrongKind struct { ID string Want EntityKind Got EntityKind } func (e *ErrWrongKind) Error() string { if e.Got == "" { return fmt.Sprintf("identifier %q has no recognized FLUID prefix, wanted a %s id", e.ID, e.Want) } return fmt.Sprintf("identifier %q is a %s id, wanted a %s id", e.ID, e.Got, e.Want) } // RequireKind checks that id names the expected artifact. func RequireKind(id string, want EntityKind) error { got, ok := KindOf(id) if !ok || got != want { return &ErrWrongKind{ID: id, Want: want, Got: got} } return nil }