Files
member-console/internal/integration/registration.go
T
cgalo5758 dd3962990b Adopt entity keys and add invoice numbers
Replace the entity slugs on organizations, workspaces, resource pools,
and
plan ladders with nullable `key` columns and add keys to products,
prices,
and entitlement sets. Rename `providers.slug` to `provider` and add
partial
unique indexes for system and org role names.

Assign invoice numbers per billing account from a gapless transactional
counter; Stripe's number moves to the invoice mapping as an external
reference.

Seeds, fixtures, and the operator lookup address rows by key, and the
returning-login resync no longer blanks a display name when the IdP
sends
no `name` claim.
2026-08-29 20:12:04 -05:00

238 lines
7.9 KiB
Go

package integration
import (
"context"
"database/sql"
"fmt"
"regexp"
"strings"
)
var keyPattern = regexp.MustCompile(`^[a-z0-9]+$`)
// statePattern is a hygiene format for lifecycle state names. It is deliberately
// permissive (allows underscores, e.g. a future `quota_locked`) because the state
// vocabulary is per-provider, not a closed global enum.
var statePattern = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)
var validProviderKinds = map[ProviderKind]bool{
KindPayment: true,
KindProvisioning: true,
KindNotification: true,
KindTax: true,
}
var validOperations = map[Operation]bool{
OpCreate: true,
OpSetStatus: true,
OpDelete: true,
OpList: true,
OpDescribe: true,
}
// validateManifest enforces the parts of the provider contract that are
// checkable from the manifest alone: key hygiene, a known kind, known verbs,
// and key-prefix ownership of every declared resource key.
func validateManifest(m Manifest) error {
if !keyPattern.MatchString(m.Key) {
return fmt.Errorf("provider key %q must match ^[a-z0-9]+$ (no underscores)", m.Key)
}
if !validProviderKinds[m.Kind] {
return fmt.Errorf("provider %q: unknown provider_kind %q", m.Key, m.Kind)
}
declaresSetStatus := false
for _, op := range m.Operations {
if !validOperations[op] {
return fmt.Errorf("provider %q: unknown operation %q", m.Key, op)
}
if op == OpSetStatus {
declaresSetStatus = true
}
}
// State-set agreement: a provider declaring set_status must declare the
// implicit `active` baseline plus at least one non-active state; a provider
// without set_status must declare no states. (See provider-registry spec.)
hasActive, hasNonActive := false, false
for _, s := range m.States {
if !statePattern.MatchString(string(s)) {
return fmt.Errorf("provider %q: state %q must match ^[a-z][a-z0-9_]*$", m.Key, s)
}
if s == StateActive {
hasActive = true
} else {
hasNonActive = true
}
}
if declaresSetStatus {
if !hasActive {
return fmt.Errorf("provider %q: declares set_status but does not declare the `active` state", m.Key)
}
if !hasNonActive {
return fmt.Errorf("provider %q: declares set_status but declares no non-active state", m.Key)
}
} else if len(m.States) > 0 {
return fmt.Errorf("provider %q: declares lifecycle states but does not declare the set_status operation", m.Key)
}
prefix := m.Key + "_"
for _, key := range m.ResourceKeys {
if !strings.HasPrefix(key, prefix) {
return fmt.Errorf("provider %q: owned resource key %q must be prefixed %q", m.Key, key, prefix)
}
}
return nil
}
// leadingToken returns the substring before the first underscore (the whole
// string if there is none).
func leadingToken(key string) string {
if i := strings.IndexByte(key, '_'); i >= 0 {
return key[:i]
}
return key
}
// checkKeyNesting enforces that the provider-key namespace and the platform
// (unprefixed) resource-key namespace do not collide: no provider key may be
// the leading underscore-delimited token of a platform key, which would let one
// provider's "<key>_" prefix swallow a platform-owned key. (Provider-key vs
// provider-key nesting is already impossible because provider keys carry no
// underscore.)
func checkKeyNesting(providerKeys map[string]bool, platformKeys []string) error {
for _, key := range platformKeys {
tok := leadingToken(key)
if providerKeys[tok] {
return fmt.Errorf("provider key %q nests under platform resource key %q", tok, key)
}
}
return nil
}
// RegisterProviders reconciles the provider registry from code at boot,
// parallel to how migrations are assembled from MigrationSources. It validates
// every manifest, enforces key hygiene against existing platform resource
// keys, then upserts provider + operation rows and stamps
// core.resource_keys.provider for each owned key — all in one
// transaction. It is idempotent: re-running with the same sources only
// refreshes capability fields and reconciles the declared operation set.
// Operational state (provider status) is never clobbered.
//
// NOTE: the contract's "every declared operation resolves to an implemented
// activity" check is only partially realizable here. The manifest is the
// declaration and verb validity is enforced by validateManifest; binding each
// declared verb to its registered Temporal activity lands with the
// outbox→activity dispatch layer (M8c and later).
func RegisterProviders(ctx context.Context, db *sql.DB, sources []ProviderSource) error {
manifests := make([]Manifest, 0, len(sources))
providerKeys := make(map[string]bool, len(sources))
for _, src := range sources {
m := src.ProviderManifest()
if err := validateManifest(m); err != nil {
return err
}
if providerKeys[m.Key] {
return fmt.Errorf("duplicate provider key %q", m.Key)
}
providerKeys[m.Key] = true
manifests = append(manifests, m)
}
owned := make(map[string]bool)
for _, m := range manifests {
for _, k := range m.ResourceKeys {
owned[k] = true
}
}
q := New(db)
platformKeys, err := q.ListPlatformResourceKeys(ctx)
if err != nil {
return fmt.Errorf("listing platform resource keys: %w", err)
}
// A provider's own keys may still be NULL-provider at this point (freshly
// renamed into place, or about to be stamped below), so they surface as
// platform-owned. Exclude declared-owned keys: only genuinely platform-owned
// keys participate in the key-nesting check.
platform := platformKeys[:0]
for _, k := range platformKeys {
if !owned[k] {
platform = append(platform, k)
}
}
if err := checkKeyNesting(providerKeys, platform); err != nil {
return err
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
qtx := q.WithTx(tx)
for _, m := range manifests {
if _, err := qtx.UpsertProvider(ctx, UpsertProviderParams{
// The registry column is `provider` (migration 00014, upstream's
// ratified name); the manifest field is the Go contract's Key.
Provider: m.Key,
ProviderKind: string(m.Kind),
DisplayName: m.DisplayName,
OperatorSurfacePath: sql.NullString{String: m.OperatorSurfacePath, Valid: m.OperatorSurfacePath != ""},
}); err != nil {
return fmt.Errorf("upsert provider %q: %w", m.Key, err)
}
ops := make([]string, 0, len(m.Operations))
for _, op := range m.Operations {
ops = append(ops, string(op))
if err := qtx.AddProviderOperation(ctx, AddProviderOperationParams{
Provider: m.Key,
Operation: string(op),
}); err != nil {
return fmt.Errorf("add operation %q for %q: %w", op, m.Key, err)
}
}
// Reconcile: drop any operation this provider no longer declares.
if err := qtx.DeleteProviderOperationsNotIn(ctx, DeleteProviderOperationsNotInParams{
Provider: m.Key,
Operations: ops,
}); err != nil {
return fmt.Errorf("reconcile operations for %q: %w", m.Key, err)
}
states := make([]string, 0, len(m.States))
for _, s := range m.States {
states = append(states, string(s))
if err := qtx.AddProviderState(ctx, AddProviderStateParams{
Provider: m.Key,
State: string(s),
}); err != nil {
return fmt.Errorf("add state %q for %q: %w", s, m.Key, err)
}
}
// Reconcile: drop any state this provider no longer declares (an empty
// declared set removes all rows — correct for a provider that dropped
// set_status).
if err := qtx.DeleteProviderStatesNotIn(ctx, DeleteProviderStatesNotInParams{
Provider: m.Key,
States: states,
}); err != nil {
return fmt.Errorf("reconcile states for %q: %w", m.Key, err)
}
for _, key := range m.ResourceKeys {
n, err := qtx.SetResourceKeyProvider(ctx, SetResourceKeyProviderParams{
ResourceKey: key,
Provider: sql.NullString{String: m.Key, Valid: true},
})
if err != nil {
return fmt.Errorf("stamp resource key %q for %q: %w", key, m.Key, err)
}
if n == 0 {
return fmt.Errorf("provider %q declares resource key %q, but no such row exists in core.resource_keys", m.Key, key)
}
}
}
return tx.Commit()
}