appbuild

package
v0.0.0-...-74c8360 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: AGPL-3.0 Imports: 51 Imported by: 0

Documentation

Overview

Package appbuild assembles the focused services every project entry point (rela-server, rela-desktop, future bindings) needs from a project directory. It replaces the legacy workspace.Discover path for those entry points: callers receive a Services holding individually-constructed collaborators (store, metamodel, entitymanager, searcher, tracer, validator, templater, config loader, state KV) rather than a god-object.

What's not here, and why:

  • lua.WriteDeps: derived per-invocation from the static lua read deps plus the per-call write handle. Built by callers that actually invoke scripts (scheduler tick, script command, automation cascade) — see Services.LuaWriteDeps.
  • lua.Cache: an implementation detail of *script.Engine. Callers that need it ask the engine via Services.ScriptEngine.
  • File watching: each domain owns its own watch story (fsstore self-watches; dataentry subscribes to data-entry.yaml). Services has no watcher methods.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewElevationAuditor

func NewElevationAuditor(sink audit.Audit) lua.ElevationRecorder

NewElevationAuditor returns the elevated-read audit recorder over sink, or a genuinely nil interface when no sink is wired (which the lua side reads as "recording disabled").

Exported so appbuildtest can build the SAME elevation bundle production does — a fixture that granted elevated reads without the audit trace would let a test pass while the wiring it stands in for has the audit gap.

This wrapper exists for ONE reason: the nil conversion. audit's constructor returns *audit.ElevationRecorder, and assigning a nil one straight into lua's interface-typed ElevationRecorder field yields a TYPED nil, which is != nil — lua's `ElevationRecorder == nil` guard would pass and the first elevated read would nil-deref, killing the process on a path that is supposed to degrade quietly. Returning the INTERFACE type here makes the nil a real nil. Same trap as RR-5QQL1Z.

func NextActionMatchers

func NextActionMatchers(
	cfg *dataentryconfig.Config, meta *metamodel.Metamodel,
) (lookup func(string) (nextaction.Matcher, bool), problems []string)

NextActionMatchers compiles the `condition:` of every next-action source, adapting conditionlint's compiler to the seam internal/dataentry declares.

Lives at the composition root for the same reason the userstate backend does: the condition/policy engine sits above the data-entry app, so dataentry takes a seam and this bridges the two.

Takes config + metamodel rather than returning a prebuilt lookup because both reload at runtime — a lookup captured at boot would keep evaluating a condition the operator has since edited.

Types

type Collaborators

type Collaborators struct {
	FS       storage.FS
	Paths    *project.Context
	Meta     *metamodel.Metamodel
	Store    store.Store
	Searcher search.Searcher
	// VisibleSearcher may be nil: the constructor then derives the
	// generic search.NewVisible(Searcher, Store) wrapper, which is the
	// correct implementation for every in-process store. Only wire it
	// explicitly to exercise a native implementation.
	VisibleSearcher search.VisibleSearcher
	EntityManager   *entitymanager.Manager
	Tracer          tracer.Tracer
	Validator       validator.Validator
	Templater       templating.Templater
	CfgLoader       config.Loader
	StateKV         state.KV
	ScriptEngine    *script.Engine
	ACL             acl.ACL
	Audit           audit.Audit

	// Declarative is the optional concrete *acl.Declarative the test
	// is wiring. When non-nil, [Services.ACLDeclarative] returns it;
	// the affordance resolver path then composes against the same
	// resolver the write path uses (RR-FGJR). When nil — typical when
	// ACL is [acl.NopACL] or [acl.ReadOnlyACL] —
	// [Services.ACLDeclarative] returns nil and the dataentry
	// resolver selector falls through to [NopFieldVerdictResolver].
	//
	// If you set Declarative, ACL must reference the same value
	// (typically ACL == Declarative). The constructor enforces this.
	Declarative *acl.Declarative

	// SearchCloser may be nil — see type doc.
	SearchCloser io.Closer
}

Collaborators bundles the fully-built dependencies of a Services instance. Exposed so external test fixtures (`appbuildtest`) and alternative composition roots can assemble a Services without poking at unexported fields. Production callers go through New / Discover instead.

Every field is required. NewFromCollaborators validates them. The production wiring builds a Services from a real filesystem, real metamodel, real entity manager, etc.; test fixtures supply in-memory equivalents (see `appbuildtest`). There is no production code path that runs without a complete Services — making any of these optional would force every downstream consumer to nil-check what it depends on.

The one nuance: SearchCloser may be nil when the search backend does not own a closable resource (the error-Searcher placeholder has nothing to close).

type Config

type Config struct {
	FS           storage.FS
	Paths        *project.Context
	ScriptEngine *script.Engine
	Audit        audit.Audit

	// DatabaseURL is the PostgreSQL connection string. The caller decides
	// where it comes from: [Discover] takes it from [WithDatabaseURL] or, as a
	// fallback, $RELA_DATABASE_URL; a caller building a Config directly (e.g.
	// rela-desktop, or a per-tenant lookup) simply sets it.
	//
	// The invariant is that a DSN must **never reach a command line** — it
	// carries a password, and a flag would put it in `ps` output and shell
	// history. That is why no binary exposes a --database-url flag. It is NOT
	// an invariant that the value come from the environment; passing it in Go
	// code is fine and is what makes two differently-backed Services
	// constructible in one process.
	//
	// Consumed only by the postgres build; empty (and ignored) in the
	// FS/memory builds.
	DatabaseURL string
}

Config carries the inputs every build of New needs, plus backend-specific configuration that only some builds consume.

The build-agnostic fields (FS, Paths, ScriptEngine, Audit) are required by every scenario — even the postgres build still reads the metamodel and templates from the filesystem (see Paths). DatabaseURL is consumed only by the postgres build and ignored by the FS and memory builds; this is the seam where backend-specific configuration enters the composition root without forcing other builds to acknowledge it through shared parameters.

type GatedGraphReader

type GatedGraphReader interface {
	GetEntity(ctx context.Context, id string) (*entity.Entity, error)
	ListEntities(ctx context.Context, q store.EntityQuery) iter.Seq2[*entity.Entity, error]
	GetRelation(ctx context.Context, from, relType, to string) (*entity.Relation, error)
	ListRelations(ctx context.Context, q store.RelationQuery) iter.Seq2[*entity.Relation, error]
	CountEntities(ctx context.Context, q store.EntityQuery) (int, error)
	CountRelations(ctx context.Context, q store.RelationQuery) (int, error)
}

GatedGraphReader is the row-and-tally read surface returned by Services.GatedReads. Row reads are ACL-gated; the two counts are not — see [gatedGraphReader] for why.

type GatedReadBundle

type GatedReadBundle struct {
	Reader    GatedGraphReader
	Tracer    tracer.Tracer
	Validator validator.Validator
}

GatedReadBundle is the result of Services.GatedReads: the three read handles an identity-bearing consumer needs, each ACL-bound to the ctx principal at call time.

type Option

type Option func(*options)

Option configures construction of a Services bundle. Options are optional; production callers typically pass none. Used by entry points that need to swap a focused collaborator at startup — today, `rela-server --read-only` injects acl.ReadOnlyACL via WithACL.

func WithACL

func WithACL(a acl.ACL) Option

WithACL overrides the auto-loaded ACL with the supplied implementation. Default behavior (no option) is to load `acl.yaml` from the project root via acl.LoadPolicy; on `os.ErrNotExist` the default falls back to acl.NopACL (allow-all). WithACL is how `rela-server --read-only` injects acl.ReadOnlyACL: the option always wins, even when an `acl.yaml` is present, so the flag is an unconditional override.

Tests should prefer NewForTest + WithTestACL over driving this path directly.

func WithDatabaseURL

func WithDatabaseURL(dsn string) Option

WithDatabaseURL supplies the PostgreSQL DSN explicitly, instead of letting Discover read it from $RELA_DATABASE_URL. The option always wins over the environment, so a caller that knows where a project's data lives never has to mutate process state to say so.

This exists so that *which database a project uses* is an argument rather than an ambient property. Two Services can then be constructed in one process against different databases — impossible via the environment, which is global and shared. `rela-desktop` already builds a fresh bundle per project switch, and a future multi-tenant server resolves a DSN per tenant.

The invariant this must not break: a DSN carries a password, so it must never reach a command line. Passing one here in Go code is fine — sourcing it from a flag is not. See Config.DatabaseURL.

Ignored by the FS and memory builds, which have no DSN.

type Services

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

Services exposes the focused collaborators a project entry point needs as method accessors. Construct via Discover or New.

Method form (not exported fields) is the established pattern in this codebase: it lets *Services satisfy consumer-side service interfaces — `scheduler.WorkspaceProvider`, the data-entry app's constructor inputs — through structural typing, without adapters at the wiring site.

Every exported method is a one-line accessor for a collaborator this facade constructs; the count tracks the number of wired services, not an accreting public API. Versions() (TKT-N0IKN9) took it to 21 — a documented facade exception, not a ratchet target. ScheduledLuaWriteDeps() (TKT-ZF2DTV) takes it to 22: it is a scheduler.WorkspaceProvider interface method, so it must be exported; the two redactor-parameterized variants behind it are unexported precisely to keep this surface from growing by three. CalDAVAliases() (TKT-WAA092) takes it to 23: the alias service is constructed here and consumed by cmd/rela-server when wiring the data-entry App, so it has to cross the package boundary — there is no in-package caller to hide it behind. GatedReads() (TKT-UIR41P) takes it to 24: it returns the ACL-bound read handles an identity-bearing consumer needs, and it is deliberately ONE method returning a bundle rather than three accessors — the three handles must come from the same gate to stay consistent, and splitting them would both grow this surface by three and let a caller mix a gated reader with a raw tracer. UserState() (TKT-CXD0A4) takes it to 25, for the same reason as the others: the backend is chosen here (per build tag) and handed to the App at the wiring site.

The exported count is the service-accessor surface: Services IS the wiring facade, so each new subsystem it composes adds one getter. Ratchet this down by splitting the bundle (TKT-N0IKN9), not by hiding an accessor.

Develop raised this to 26; the three recipient-scoped scheduler methods take it to 29. They are exported because scheduler consumes them through narrow capability interfaces; they do not add general Services getters.

func Discover

func Discover(startDir string, scriptEngine *script.Engine, opts ...Option) (*Services, error)

Discover resolves the project at startDir and constructs every service the entry points need. scriptEngine is the long-lived Lua engine; production callers pass script.NewEngine.

Discover constructs a production audit.Filesystem under .rela/audit/ and resolves the database URL (postgres build) from WithDatabaseURL when supplied, falling back to the RELA_DATABASE_URL environment variable. Neither source is a command-line flag, which is the property that matters: a DSN carries a password and must not land in `ps` output or shell history. The entry point caller is responsible for stamping [principal.Principal] onto the request context (this varies per binary — cli, mcp, scheduler, data-entry server).

func New

func New(cfg Config, opts ...Option) (*Services, error)

New builds the services bundle for the default (filesystem) build: an fsstore rooted at the project paths plus an in-memory bleve search index wired as a write observer. This is the per-scenario recipe — it owns only the backend choice; [prepare] and [assemble] do the build-agnostic work shared by every build.

func NewFromCollaborators

func NewFromCollaborators(c Collaborators) (*Services, error)

NewFromCollaborators assembles a Services from pre-built collaborators. Used by external test packages that want to swap individual collaborators (e.g. inject a fake store) without going through the full production wiring of New.

Returns an error when any required field is nil. See Collaborators for the contract.

func (*Services) ACL

func (s *Services) ACL() acl.ACL

ACL returns the authorization gate wired into entitymanager. Exposed so entry points (rela-server) can render operator warnings based on the active policy — e.g. "non-loopback bind without an acl.yaml" — without re-reading the file. The returned value is the exact ACL the Manager consults.

func (*Services) ACLDeclarative

func (s *Services) ACLDeclarative() *acl.Declarative

ACLDeclarative returns the concrete *acl.Declarative when the wired ACL is one (the default when acl.yaml is present and parses); nil when ACL is NopACL or a test injected something else via WithACL.

Exposed so the affordance resolver can be built with the same Declarative the Manager uses — keeping the group expansion, containment, and Source attribution consistent across write authz and affordance verdicts. The field is set at construction time alongside `acl`; no runtime type assertion at the accessor.

func (*Services) ACLPolicy

func (s *Services) ACLPolicy() *acl.Policy

ACLPolicy returns the *acl.Policy parsed from acl.yaml, or nil when no policy file was present or the ACL was injected via WithACL. Exposed so the data-entry server can build the policy-backed affordance resolver from the same policy the Manager authorizes against, without re-reading the file.

func (*Services) Audit

func (s *Services) Audit() audit.Audit

Audit returns the audit sink wired into entitymanager. Exposed so dataentry handlers can emit `denied-write` rows for short-circuit rejections (affordance gates) that never reach the manager.

func (*Services) CalDAVAliases

func (s *Services) CalDAVAliases() *caldavalias.Service

CalDAVAliases is the CalDAV<->rela resource alias service. Never nil: the service is always constructed (an empty table is the normal first-run state), so consumers need no nil check.

func (*Services) Close

func (s *Services) Close() error

Close releases resources held by Services: store first (so any in-flight observer callbacks complete), then the search backend.

Safe to call repeatedly and from multiple goroutines; the close sequence runs exactly once. Subsequent calls return the same nil (no errors are returned from the close path today — store close failures are slog.Warn'd).

func (*Services) Config

func (s *Services) Config() config.Loader

Config returns the project's data-entry config loader.

func (*Services) EntityManager

func (s *Services) EntityManager() *entitymanager.Manager

EntityManager returns the production write path.

The concrete *entitymanager.Manager, not an interface: it is the sole implementation, and handing out the concrete type lets each consumer declare its own narrow write interface at its call site (CLAUDE.md "interfaces at the call site") and be satisfied structurally, with no wiring change here. It is also what lets internal/cli assert the id-preserving sync applier against a concrete type rather than interface-to-interface (TKT-IVSJV6).

func (*Services) FS

func (s *Services) FS() storage.FS

FS returns the project filesystem.

func (*Services) GatedReads

func (s *Services) GatedReads() GatedReadBundle

GatedReads returns the read handles bound to whatever principal is on the ctx AT CALL TIME — the reader, the traversal handle, and a validator whose candidate set comes from that same gated reader.

This is the read bundle for an identity-bearing, non-HTTP consumer. The MCP server is the caller: its handlers, resources, prompts, analyze and export surfaces all read through the returned reader, so gating is decided once here rather than per handler (DEC-ZBI39P).

The validator matters as much as the reader. `Services.Validator()` is built over the RAW store, which is right for the unattended paths that own it — but a validation rule evaluated for a requester must not read rows the requester cannot see, or a hidden value reaches a violation message (TKT-3FL2S6). So this builds a second validator over the gated reader.

Identity is deliberately NOT captured here: the wrapped handles resolve the ctx principal per call, so ONE bundle serves every request. Under NopACL (no acl.yaml) these are the raw store/tracer — byte-identical to pre-ACL behavior, not a bypass. A construction failure REFUSES via visibility.DenyReader / DenyTracer rather than degrading to raw reads (RR-GKCZO5).

Gating is BOTH row-level and field-level (TKT-425426): the reader prunes entities the principal may not see, and redacts `visible:`-hidden ENTITY PROPERTY values on the ones it returns. The validator is built over that same reader, so a violation message cannot quote a value the requester cannot read (the field-level half of TKT-3FL2S6).

SCOPE: field redaction covers entity properties only. Relation meta is NOT redacted here — [gatedGraphReader.GetRelation] reads raw, and relation-level `visible:` grants (acl.RelationGrant.Visible, honored on the dataentry wire via affordances.RelationFieldVerdicts) are not consulted. Relations are gated at the ROW level on both endpoints; their properties are not. Do not read the paragraph above as covering them. Tracked as TKT-0RBFN0 (IB-review #1 on PR #1400), which also covers ListRelations: that path IS row-gated, but visibility.PolicyReader implements only FilterRelations, so a surviving edge still carries all of its meta.

func (*Services) Jobs

func (s *Services) Jobs() jobs.Client

Jobs returns the background-job queue, already started.

The return type deliberately omits jobs.Lifecycle: starting and stopping the queue is the composition root's concern, and a consumer that could Close it would be shutting background work down for every other subsystem. Services keeps the lifecycle handle privately and tears it down in Close.

Register handlers at wiring time; the dispatcher resolves a handler per job, so registration after start is fine. Durability depends on the build — ephemeral on fs/desktop, durable on postgres.

Nil: never — assemble fails rather than returning a Services with no queue, since a nil queue would turn every Enqueue into a panic at the call site rather than a wiring error here.

func (*Services) LuaReadDeps

func (s *Services) LuaReadDeps() lua.ReadDeps

LuaReadDeps materializes the read-only Lua capability bundle with UNRESTRICTED reads — the operator-trust-boundary wiring used by the CLI and the docs runtime, where whoever runs the binary already has the project files (RR-17DMC).

Request-scoped and scheduled callers must use Services.luaReadDepsFor instead, which binds reads to an identity (DEC-O59WM4).

Cheap to call; rebuild per-runtime so future metamodel reloads propagate.

func (*Services) LuaWriteDeps

func (s *Services) LuaWriteDeps() lua.WriteDeps

LuaWriteDeps materializes the read-write Lua capability bundle with UNRESTRICTED reads (see Services.LuaReadDeps for when that is right). EntityManager goes in as the concrete *entitymanager.Manager; the lua.WriteDeps.EntityManager field is narrower (lua.Mutator) and accepts any structural match.

func (*Services) Meta

func (s *Services) Meta() *metamodel.Metamodel

Meta returns the loaded metamodel.

func (*Services) Paths

func (s *Services) Paths() *project.Context

Paths returns the project context (root, metamodel path, etc.).

func (*Services) RunScheduledTemplate

func (s *Services) RunScheduledTemplate(ctx context.Context, name, recipientID string) error

RunScheduledTemplate renders and sends one message under the principal already installed by scheduler's recipient child handler.

func (*Services) ScheduledForEachEntities

func (s *Services) ScheduledForEachEntities(
	ctx context.Context, entityType string, where []string, limit int,
) (ids []string, dropped int, err error)

ScheduledForEachEntities resolves the bounded recipient/subject selection through the scheduler identity's existing visible reader.

func (*Services) ScheduledForEachPrincipal

func (s *Services) ScheduledForEachPrincipal(ctx context.Context, entityID string) (string, error)

ScheduledForEachPrincipal maps a selected user entity to the principal used by ACL. The entity ID is the effective principal after the normal principal_property resolution; no raw identity or role is trusted from the durable payload.

func (*Services) ScheduledLuaWriteDeps

func (s *Services) ScheduledLuaWriteDeps() lua.WriteDeps

ScheduledLuaWriteDeps satisfies scheduler.WorkspaceProvider. Reads are ACL-bound to the task's principal (stamped on the per-task ctx), with privileges coming from acl.yaml — a job sees what its identity may see, nothing more (DEC-O59WM4).

Field-level `visible:` redaction APPLIES here (TKT-0XL8MF): a job whose identity may read `person` receives that entity with the same properties redacted as a human with the same role sees in the UI. This closed RR-7408F5, which documented the earlier row-gating-only behavior.

func (*Services) ScriptEngine

func (s *Services) ScriptEngine() *script.Engine

ScriptEngine returns the Lua script engine. Callers that need the engine's shared lua.Cache (for lua.WithCache when building runtimes directly) reach it via script.Engine.LuaCache.

func (*Services) Searcher

func (s *Services) Searcher() search.Searcher

Searcher returns the search service (a sentinel error-searcher when the search backend failed to construct).

func (*Services) State

func (s *Services) State() state.KV

State returns the .rela cache-directory KV (or a sentinel error-KV when no cache dir is available).

func (*Services) Store

func (s *Services) Store() store.Store

Store returns the authoritative store.

func (*Services) Templater

func (s *Services) Templater() templating.Templater

Templater returns the entity/relation template service.

func (*Services) Tracer

func (s *Services) Tracer() tracer.Tracer

Tracer returns the graph-traversal service.

func (*Services) UserState

func (s *Services) UserState() userstate.Store

UserState returns the next-action per-user state backend (snooze / mute / cooldown).

Build-agnostic today: every build gets the in-memory backend, because this state is disposable — losing it costs a user one repeated suggestion, not data. When a durable backend lands it becomes a per-recipe choice like the store, and only the recipes change; this accessor and its consumers do not.

func (*Services) ValidateScheduledMailRecipients

func (s *Services) ValidateScheduledMailRecipients(ctx context.Context) error

ValidateScheduledMailRecipients checks current graph addresses selected by template tasks. Validation is operator-scoped and therefore scans the raw store; rendering remains recipient ACL-scoped at execution.

func (*Services) Validator

func (s *Services) Validator() validator.Validator

Validator returns the entity validator wired to the store + meta + Lua read deps.

func (*Services) Versions

func (s *Services) Versions() store.VersionService

Versions returns the content-versioning service, or nil on a build without versioning (fs/mem). Consumers must nil-check and bind the narrow sub-interface they use (history read, purge, …) rather than the umbrella.

func (*Services) VisibleSearcher

func (s *Services) VisibleSearcher() search.VisibleSearcher

VisibleSearcher returns the ACL-scoped search seam. Per-backend: the generic scope-filter wrapper on the fs/memory builds, the native SQL-composed implementation on the postgres build.

type SharedBase

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

SharedBase holds the build-agnostic inputs resolved by [prepare] and consumed by [assemble]: the validated config, applied options, the resolved ACL (+ parsed policy), and the loaded metamodel. The per-scenario New recipes thread this between prepare → openBackend → assemble so the shared steps are written exactly once. SharedBase is the tenant-independent half of construction: the validated config, the applied options, the parsed `acl.yaml` policy, and the loaded metamodel. Nothing in it is derived from a store, so ONE base can be built per process and assembled against several stores.

Build it with NewSharedBase; turn it into a Services with SharedBase.Assemble (which the per-backend New recipes call for you).

What is shared and what is not

The split is NOT along the Services field list, which is the intuitive but wrong reading. `acl.Declarative` is constructed from the STORE — it needs a store-backed `acl.Graph` for group expansion and containment inheritance — so the ACL *policy* is shared while the ACL *evaluator* is per-store. Same for `lua.ReadDeps`, which closes over the store. That is precisely why ACL construction is deferred out of this type and into SharedBase.Assemble.

Shared values must not be mutated during assembly

`meta` and `aclPolicy` are POINTERS handed to every assembled Services. A mutation through either during assembly would be visible to every other consumer of the same base — a cross-tenant defect in a multi-tenant host, and a cross-project one on the desktop. Assembly only reads them (the metamodel consumers derive new values: `statemachine.Compile`, `NewEngineFromMetamodel`), and TestSharedBase_AssemblyDoesNotMutateSharedValues pins it.

func NewSharedBase

func NewSharedBase(cfg Config, opts ...Option) (*SharedBase, error)

NewSharedBase builds the tenant-independent half of construction once: validate config, apply options, parse `acl.yaml`, load and validate the metamodel. It opens no store and touches no database.

Use it when one process serves several stores from one project configuration — a multi-tenant host (RES-D54281), or any caller that would otherwise re-read and re-validate the same metamodel per store. The single-store path is New / Discover, which call this internally.

func (*SharedBase) Assemble

func (b *SharedBase) Assemble(
	st store.Store, searcher search.Searcher,
	visible search.VisibleSearcher, searchCloser io.Closer,
) (*Services, error)

Assemble wires this base against one opened store into a Services.

Call it once per store. The base is reusable: every value it holds is tenant-independent, and assembly only reads them (see the type doc). The per-backend New recipes call this for you after opening their store; a multi-store host calls it directly, once per store.

The caller owns closing the returned Services — and only that Services. Services.Close tears down the store and search closer it was assembled with, never anything belonging to the base, so evicting one assembled Services leaves the base and every sibling usable.

visible may be nil: Assemble then derives the generic search.NewVisible(searcher, st) wrapper, which is correct for every in-process store. The postgres recipe passes its native implementation.

func (*SharedBase) Meta

func (b *SharedBase) Meta() *metamodel.Metamodel

Meta returns the loaded metamodel this base was built from. Exposed so a host holding one base can answer "what schema am I serving?" without assembling a Services first.

func (*SharedBase) Paths

func (b *SharedBase) Paths() *project.Context

Paths returns the project context this base was built from.

type TransitionWiring

type TransitionWiring struct {
	Enforcer *statemachine.Set
	Guard    statemachine.Guard
	Graph    statemachine.GraphLookup
}

TransitionWiring bundles the compiled state machines and the collaborators the entitymanager needs to enforce them. Returned by CompileTransitions so every wiring site (production assemble + test fixtures) builds the enforcer the same way.

func CompileTransitions

func CompileTransitions(meta *metamodel.Metamodel, st store.Store, resolvedACL acl.ACL) (TransitionWiring, error)

CompileTransitions builds the executable state machines from the metamodel and the wiring collaborators the entitymanager needs. A metamodel with no transitions yields an empty (no-op) enforcer. Returns an error only when a machine is malformed (surfaced at boot). Exported so test fixtures wire the enforcer through the same path as production.

resolvedACL determines the guard's fail-closed posture: when it is a policy-backed *acl.Declarative, a served write that is missing its acl.Request is denied rather than allowed (RR-UOBUC). With NopACL / ReadOnlyACL there is no policy, so the guard stays inert.

Directories

Path Synopsis
Package appbuildtest provides a test fixture for assembling an appbuild.Services bundle.
Package appbuildtest provides a test fixture for assembling an appbuild.Services bundle.
Package backendtest supplies the backend a build-agnostic wiring test needs.
Package backendtest supplies the backend a build-agnostic wiring test needs.

Jump to

Keyboard shortcuts

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