Features¶
pkg/features is the feature core under props and setup
(spec 0199).
It answers three questions, each through an interface with one default
implementation, and imports nothing of GTB so it can become go/features as a
move rather than a redesign.
| Question | Role | Default |
|---|---|---|
| What does this binary offer? | Registry (declare, contribute, snapshot) |
NewRegistry(); Default() is the init-time instance |
| What was offered when the root was built? | Snapshot (immutable, ordered) |
taken by Registry.Snapshot() |
| What has the tool chosen? | Resolver → Set |
DefaultResolver(): defaults, then the tool's states |
| Is it on for this request? | Evaluator |
a Set (static); Dynamic over a Backend in a later phase |
Declaration is init-time; everything else is a value¶
A blank import runs init, and init can only reach package state, so a
package declares its feature and its contributions on features.Default().
That is the one process-wide thing here. Everything from main on works with
values: a root takes a Snapshot, resolves a Set, and hands the set down.
Nothing seals. A snapshot is an immutable copy, so a registration made after it
is not in it and a fresh snapshot sees it; the panics the earlier seals raised
are gone with them (D1). A test that needs a feature, an initialiser or a
middleware of its own builds NewRegistry() and never writes to the default
(D5); internal/repopolicy enforces that.
Descriptors, kinds and order¶
A Descriptor is an interface (FeatureID, FeatureKind, DefaultOn,
IsDynamic) so a consumer carries its own facts beside it: GTB's
props.FeatureDescriptor adds the generated-code identifiers the generator
emits. Kind classifies (builtin, forge, more to come) so "every forge" is
a query. A snapshot's order is derived from data, never from init
sequencing: descriptors that report a Rank first (GTB's built-ins, in their
declared order), then the rest by kind and ID.
Contributions and slots¶
A contribution is a value registered under a feature ID and a Slot name. The
core knows a slot is a name and a value; setup defines GTB's slots
(SlotInitialiser, SlotSubcommand, SlotInitFlag, SlotCheck,
SlotAssets, SlotMiddleware) and asserts the types on the way out.
features.Global is the ID for a contribution that applies to every feature,
which is how global middleware is registered. A Set hands out only the
contributions of enabled features; ContributionsOf[T] does the typed read,
and that is how init, doctor and the root find their initialisers,
checks, asset bundles and middleware.
The set on Props, and the root that owns the rest¶
props.New snapshots the default registry and resolves Props.Features
from Tool.Features; p.GetFeatures() is the nil-safe read (a literal
Props resolves on first read). Every static decision asks it:
p.GetFeatures().Enabled(props.AiCmd). Props.Flags is the request-time
Evaluator, defaulting to the set. The root builds its own middleware chain
(setup.MiddlewareChain) from the set and hands it down the tree, and the
init command binds each enabled feature's flags on itself and hands the run's
flags to the initialiser providers, so two roots in one process share nothing
(D3). root.WithRegistry, WithResolver and WithChain, and
props.WithFeatures, WithSet and WithFlags, are the doors through which a
test or a consumer replaces any of those parts (D12).
Resolving a set¶
Resolve(snapshot, states) applies every descriptor's default, then the
tool's states in order. Enabling an ID nothing declared is an error
(ErrUnknownFeature); disabling one is ignored and listed by Set.Ignored()
so doctor can report it (OQ2: you can switch off what you did not link, you
cannot switch on what you did not link).
Static and dynamic¶
Anything that shapes the command tree, --help, completion, doctor or a
generated project is static and reads Set.Enabled, fixed at construction.
A descriptor reporting IsDynamic() may be overridden at evaluation time by a
Backend; the built-ins never are. The dynamic half (Backend, Dynamic, the
config-store backend) lands in a later phase of the spec; Set already
implements Evaluator so a service wired with a set today takes a dynamic
evaluator later without a call-site change.