rules

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package rules is wowapi's rule/configuration engine: modules register rule points (a key, a JSON-Schema'd value, a default, allowed scopes, and whether changes require approval); values are stored as versioned rows with temporal validity; and resolution picks the most specific active value for a (tenant, org, at) — org-ancestry → tenant → platform → code default. Versions are immutable (never mutated, only superseded), so any historical `at` resolves deterministically. Contract: blueprint 02 §2.

Rule points are the ONLY sanctioned place for values that must change without a deploy (feature flags, tenant overrides); framework config holds only their platform defaults (12 §6).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type OrgAncestry

type OrgAncestry func(ctx context.Context, db database.TenantDB, orgID uuid.UUID) ([]uuid.UUID, error)

OrgAncestry resolves an org's ancestor chain (self-first) so the resolver can walk org scope upward. Implemented against the DB by the caller-provided func so kernel/rules need not import the org store.

type Point

type Point struct {
	Key              string
	Module           string
	ValueSchema      json.RawMessage // JSON Schema (validated at write + resolve)
	Default          json.RawMessage // compiled default value
	AllowedScopes    []ScopeKind
	RequiresApproval bool
	Description      string
}

Point is a registered rule point: the schema + default + policy for a key.

type Proposal

type Proposal struct {
	Key           string
	Scope         ScopeKind
	ScopeID       uuid.UUID // org id for org scope; zero otherwise
	Value         json.RawMessage
	EffectiveFrom time.Time // zero → now
}

Proposal is a requested rule value change at a scope.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry collects rule points during module registration; it is synced to rule_definitions at boot and consulted by the resolver for defaults + policy.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty rule registry.

func (*Registry) Err

func (r *Registry) Err() error

Err returns accumulated registration errors joined, or nil.

func (*Registry) Get

func (r *Registry) Get(key string) (Point, bool)

Get returns the registered point.

func (*Registry) Keys

func (r *Registry) Keys() []string

Keys returns registered keys, sorted.

func (*Registry) Points

func (r *Registry) Points() map[string]Point

Points returns the registered points keyed by key.

func (*Registry) Register

func (r *Registry) Register(module string, p Point)

Register adds a rule point. Malformed keys, a module-prefix mismatch, a missing schema/default, or a duplicate are recorded as errors surfaced by Err().

type Resolved

type Resolved struct {
	Key       string
	Value     json.RawMessage
	Scope     ScopeKind // scope the winning version was set at (or "" for the code default)
	VersionID uuid.UUID // zero when the code default won
	IsDefault bool
}

Resolved is a rule value plus provenance.

func (Resolved) Decode

func (r Resolved) Decode(out any) error

Decode unmarshals the resolved value into out.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver resolves rule values. It runs on the caller's TenantDB (one snapshot), reads active versions, and falls back to the registered default.

func NewResolver

func NewResolver(reg *Registry, ancestry OrgAncestry) *Resolver

NewResolver builds a resolver over the rule registry. ancestry may be nil (org-scope resolution then falls back to tenant/platform/default).

func (*Resolver) Resolve

func (r *Resolver) Resolve(ctx context.Context, db database.TenantDB, key string, org uuid.UUID, at time.Time) (Resolved, error)

Resolve returns the effective value of key for (tenant, org, at): the most specific active version wins — org-ancestry (nearest first) → tenant → platform → code default. Versions are immutable, so any historical `at` resolves deterministically (blueprint 02 §2.2). An unregistered key is a programming error.

type ScopeKind

type ScopeKind string

ScopeKind is the level a rule value applies at.

const (
	ScopePlatform ScopeKind = "platform"
	ScopeTenant   ScopeKind = "tenant"
	ScopeOrg      ScopeKind = "org"
)

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists rule versions. Writes run on the caller's TenantDB (draft proposals as app_rt) or a platform connection (activation as app_platform).

func NewStore

func NewStore(reg *Registry, idgen model.IDGen) *Store

NewStore builds the version store over the rule registry.

func (*Store) Activate

func (s *Store) Activate(ctx context.Context, db database.DBTX, versionID, approvedBy uuid.UUID) error

Activate approves a draft version: it supersedes any active version at the same scope and marks the draft active, recording the approver — all in one tx. Runs with platform privilege (rule activation is a kernel/platform concern). Returns an error if the version is not in draft/pending.

func (*Store) Propose

func (s *Store) Propose(ctx context.Context, db database.TenantDB, p Proposal) (uuid.UUID, error)

Propose inserts a DRAFT rule version in the caller's tenant tx (app_rt may INSERT). A draft never resolves — it must be Activate'd (a platform/kernel operation, app_platform) to take effect. This keeps rule ACTIVATION — which changes runtime behavior — off the module-facing app_rt role, consistent with the config-write posture (SEC-13). The RequiresApproval flag governs whether a human/workflow approval must precede Activate; the store mechanics are uniform.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL