Documentation
¶
Overview ¶
Package lifecycle is wowapi's STATIC provider/lifecycle manifest (backlog B9). It is deliberately NOT a runtime DI container: there is no reflection, no codegen, and no service locator. It is a small, hand-maintained descriptor model — ProviderDescriptor{Provides, Requires, Scope} — that captures the REAL provider graph wired by kernel.New (kernel/kernel.go), app.Boot/moduleContext (app/context.go, app/boot.go), and module.Context (module/module.go), plus a pure-function lint over that manifest that catches the wiring-mistake classes the framework-competitive-architecture- benchmark's "DI / IoC: Static Lifecycle Graph For Go" section calls out.
The manifest is maintained by hand alongside the wiring it describes (there is no generator reading kernel.go via reflection/AST) — Manifest() below IS the source of truth the lint runs over, and CurrentManifest's own test (TestCurrentManifestLintsClean) is the regression net: if a future change to kernel.New/app.Boot/module.Context adds a wiring mistake, either that test or a forgotten manifest update will need updating, keeping the manifest honest by review.
Index ¶
Constants ¶
const ( ClassScopeLeak = "scope_leak" // (a) process-scoped depends on request-scoped ClassRawPool = "raw_pool" // (b) module receives a raw pool instead of TxManager ClassTenantEscape = "tenant_escape" // (c) tenant-scoped service escapes its transaction ClassMigrateInAPI = "migrate_in_api" // (d) migrate-only service wired into API runtime ClassMissingProvider = "missing_provider" // (e) declared Requires has no matching Provides ClassCycle = "cycle" // (e) dependency cycle ClassDuplicate = "duplicate_provide" // manifest hygiene: two descriptors with the same Provides ClassInvalidScope = "invalid_scope" // manifest hygiene: Scope is not one of the five recognized values )
Violation classes, matching backlog B9's acceptance list verbatim so a reader can cross-reference the backlog item to the check that enforces it.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Manifest ¶
type Manifest struct {
Descriptors []ProviderDescriptor
}
Manifest is an ordered set of descriptors. Order is preserved for stable printing; lint functions are order-independent (they index by Provides).
func CurrentManifest ¶
func CurrentManifest() Manifest
CurrentManifest is the hand-maintained descriptor graph for wowapi's ACTUAL wiring, read from kernel.New (kernel/kernel.go), app.Boot/moduleContext (app/context.go, app/boot.go) and module.Context (module/module.go) as of this package's introduction (backlog B9). It is not generated: whoever adds a field to Kernel/moduleContext/moduleDeps/module.Context should add or update the matching descriptor(s) here in the same change — the CI gate (`wowapi lint lifecycle`, and TestCurrentManifestLintsClean) exists so a forgotten or wrong update fails loudly rather than silently drifting.
Naming convention: "kernel.<Field>" for a Kernel struct field built in kernel.New; "module.Context.<Method>" for an accessor module.Context exposes (all of which are APIRuntime: true, since module.Register runs in every product's api/worker boot path).
func (Manifest) ByProvides ¶
func (m Manifest) ByProvides() map[string]ProviderDescriptor
ByProvides indexes the manifest's descriptors by their Provides name. Behavior is undefined (last-wins) if two descriptors share a Provides name — checkDuplicateProvides reports that as a violation.
func (Manifest) Print ¶
Print renders the manifest as a stable, human-readable table (used by `wowapi lint lifecycle` and tests).
func (Manifest) Sorted ¶
func (m Manifest) Sorted() []ProviderDescriptor
Sorted returns a copy of the descriptors sorted by Provides, for deterministic printing/diffing.
type ProviderDescriptor ¶
type ProviderDescriptor struct {
// Provides is the unique name of the capability this descriptor wires,
// e.g. "kernel.Pool" or "module.Context.Tx". Conventionally
// "<owner>.<FieldOrMethod>" matching the real Go identifier so the
// manifest stays traceable to kernel.go/context.go/module.go.
Provides string
// Requires lists the Provides names this descriptor's construction
// depends on. Every entry must match another descriptor's Provides (lint
// class (e): missing provider) and must not form a cycle.
Requires []string
// Scope is this descriptor's lifetime.
Scope Scope
// RawPool marks a descriptor that hands out an unwrapped *pgxpool.Pool
// (or equivalent raw connection) rather than a TxManager/TenantDB. Lint
// class (b) flags any module-facing descriptor with this set.
RawPool bool
// TenantScoped marks a descriptor whose value must not outlive the
// tenant transaction that produced it (a database.TenantDB or something
// derived from one). Lint class (c) flags such a descriptor with a
// Requires edge FROM a wider-scoped (process/request) descriptor that
// would let the value escape its transaction.
TenantScoped bool
// APIRuntime marks a descriptor that is wired into the api/worker
// runtime module.Context surface (i.e. reachable from a module's
// Register). Lint class (d) flags a migrate-scoped descriptor with
// APIRuntime set — a migrate-only service must never be wired into a
// process that serves API/worker traffic.
APIRuntime bool
}
ProviderDescriptor describes one provided value/capability in the wiring graph: what it provides, what it requires (by Provides name of another descriptor), and the scope it is valid at.
type Scope ¶
type Scope string
Scope is the lifetime a provided value is valid for. Mirrors the benchmark doc's recommended Go-native shape (framework-competitive-architecture- benchmark.md, "DI / IoC: Static Lifecycle Graph For Go").
const ( // ScopeProcess: constructed once in kernel.New, lives for the process // lifetime (pools, registries, evaluators, kernel services). ScopeProcess Scope = "process" // ScopeRequest: constructed per inbound HTTP request (module.Context // accessors handed to Module.Register are process-scoped services, but // request-handling code that consumes them operates per-request). ScopeRequest Scope = "request" // ScopeTenantTx: valid only for the lifetime of one tenant-bound // transaction opened via database.TxManager.WithTenant/WithTenantRO // (database.TenantDB). Must never escape the callback that receives it. ScopeTenantTx Scope = "tenant_tx" // ScopeJob: constructed for one worker job execution. ScopeJob Scope = "job" // ScopeMigrate: valid only in the migrate process (privileged DDL // connection, app.SkipRLSEnforcementCheck). Must never be wired into a // process that serves API/worker runtime traffic. ScopeMigrate Scope = "migrate" )
type Violation ¶
Violation is one lint finding. Class identifies which failure category (a-e, matching the backlog B9 acceptance list) so callers/tests can filter or count by class; Provides names the offending descriptor; Message is the human-readable detail.