140 lines
7.8 KiB
Markdown
140 lines
7.8 KiB
Markdown
# Configuration Internals
|
|
|
|
User-visible fields, defaults, and selection behavior belong in the
|
|
[Configuration Reference](../config.md). This document describes the internal
|
|
pipeline-loading boundary implemented by `internal/config`.
|
|
|
|
## Pipeline Loading
|
|
|
|
`LoadPipeline` assembles and validates a pipeline in this order:
|
|
|
|
1. Parse the root YAML into a presence-aware composition tree. The tree retains
|
|
source names, full field paths, node kinds, declaration order, and explicit
|
|
zero, false, empty-map, and empty-list values.
|
|
2. Remove the root-only `composition` envelope and validate its explicit
|
|
`imports`, `default_profile`, and named `profiles` declarations. A load
|
|
option retains the difference between omitted and explicitly empty profile
|
|
selection.
|
|
3. Open each import relative to the root pipeline directory through the
|
|
confined regular-file boundary. Imports must use a `.yml` or `.yaml`
|
|
extension and cannot traverse, use symlinks, repeat a file, import the root,
|
|
or contain another composition envelope.
|
|
4. Resolve and structurally parse every declared profile overlay through the
|
|
same confined regular-file boundary. Missing or malformed unselected
|
|
overlays fail the load. Overlays cannot contain a composition envelope.
|
|
5. Additively merge the root body and imports. Distinct map leaves compose;
|
|
repeated scalar or list paths and node-kind disagreements are conflicts.
|
|
6. Select exactly one declared profile from an explicit option or the default,
|
|
then recursively merge its overlay. Overlay leaves replace base leaves,
|
|
lists are atomic replacements, and null or kind changes fail.
|
|
7. Emit deterministic canonical YAML and strictly decode it into
|
|
`PipelineConfig`.
|
|
8. Apply pipeline defaults once, resolve ordinary relative pipeline paths from
|
|
the root pipeline file, and digest the normalized effective mapping.
|
|
|
|
This ordering preserves monolithic configuration behavior. Moving a field to
|
|
an imported fragment changes its source ownership, not its path base, default,
|
|
or schema semantics.
|
|
|
|
## Loaded Context Resolution
|
|
|
|
`LoadedPipelineCampaign` carries one already composed pipeline and its selected
|
|
campaign into session resolution. `LoadSessionWithPipelineCampaignOptions`
|
|
loads a local session against that context, while
|
|
`ResolveLoadedPipelineCampaign` also accepts an already loaded remote session
|
|
or no session while a caller retrieves one. Compatibility loaders route through
|
|
these functions after their initial pipeline and campaign reads.
|
|
|
|
Application commands own pipeline and campaign discovery, campaign-file versus
|
|
registry selection, and the corresponding mutual-exclusion rules. Once they
|
|
have a `LoadedPipelineCampaign`, local session discovery and remote-session
|
|
download retain that exact pipeline object and its private provenance. Removing
|
|
a temporary downloaded session file therefore cannot invalidate the resolved
|
|
pipeline or campaign context.
|
|
|
|
Campaign context construction also reads and classifies the campaign-owned
|
|
party source through `ParseParty`. A canonical party retains its raw bytes and
|
|
normalized roster in runtime-only `ResolvedParty` provenance, while a legacy
|
|
party remains opaque. Canonical resolution creates a virtual
|
|
`derived_from_party` players input and rejects competing campaign or session
|
|
players files and session party overrides. The compact legacy compatibility
|
|
path resolves the effective campaign/session party and players files together.
|
|
|
|
## Canonical Party Domain
|
|
|
|
`ParseParty` is the package-owned boundary for classifying a party source.
|
|
When a top-level `schema_version` is present, it strictly validates the
|
|
`narratio.party.v1` contract into ordered character domain values. The
|
|
canonical value retains a separate exact byte copy of its source so consumers
|
|
can materialize the authored party document without reserializing it. Its
|
|
`PlayersYAML` method deterministically derives the versioned players-only
|
|
projection.
|
|
|
|
An unversioned source is classified by the small legacy compatibility boundary
|
|
in `party_legacy.go`; it deliberately exposes no parsed roster information.
|
|
That boundary exists solely to isolate removable compatibility behavior from
|
|
the canonical parser.
|
|
|
|
## Diagnostics And Runtime Metadata
|
|
|
|
Syntax, duplicate-key, composition, conflict, and schema failures include the
|
|
relevant source name and full field path. Additive conflicts report every
|
|
claiming source so operators can repair the split without repeatedly
|
|
rediscovering additional conflicts.
|
|
|
|
The loaded pipeline retains private runtime metadata for the absolute root
|
|
path, ordered imports, selected profile name and selection source, selected
|
|
overlay, contributing sources, effective digest, and leaf ownership. Base
|
|
leaves retain their root/import owners, replaced leaves belong to the selected
|
|
overlay, and centrally supplied values use the synthetic `default` owner. This
|
|
metadata does not participate in YAML decoding or alter the public
|
|
configuration model.
|
|
|
|
The effective digest is SHA-256 over deterministic canonical YAML produced from
|
|
the defaulted and path-resolved `PipelineConfig`. Because composition and
|
|
resolution metadata are private, the digest excludes source layout, profile
|
|
name, and ownership. Configuration stores environment variable names rather
|
|
than resolving raw credentials, so raw secret values are neither loaded nor
|
|
hashed. `recomputePipelineEffectiveDigest` is the single package-owned refresh
|
|
point for later runtime expansion.
|
|
|
|
## Test Surfaces
|
|
|
|
`composition_test.go` protects the presence and merge algebra independently of
|
|
the public schema. `pipeline_composition_test.go` exercises explicit imports,
|
|
confinement, conflicts, strict decoding, metadata, and root-relative path
|
|
behavior through `LoadPipeline`. `pipeline_profiles_test.go` covers selection,
|
|
all-overlay validation, overlay behavior, provenance, option propagation, and
|
|
effective-digest stability. Application configuration-loader tests protect the
|
|
single-read boundary by changing the pipeline file after its initial load and
|
|
confirming local session resolution retains the original pipeline. Other
|
|
configuration tests continue to protect defaults and validation after assembly.
|
|
`party_test.go` protects the versioned party schema, domain invariants, and
|
|
deterministic players projection without involving campaign or runtime wiring.
|
|
`party_resolution_test.go` protects campaign-owned party loading, canonical
|
|
input restrictions, legacy overrides, source provenance, and virtual players
|
|
input selection.
|
|
|
|
## Artifact Family Resolution
|
|
|
|
Pipeline loading retains `scriptorium.artifact_families` as a resolution-only
|
|
declaration. Once campaign party resolution establishes a canonical roster,
|
|
configuration expands families in sorted family-key and character-ID order
|
|
into ordinary `ScriptoriumArtifactConfig` values. The expansion owns the narrow
|
|
`{character_id}` output substitution, closed member-variable selectors, key and
|
|
output collision checks, and the runtime-only family-origin catalog. It then
|
|
removes family declarations from `ScriptoriumConfig`, runs ordinary Scriptorium
|
|
validation, and refreshes the effective pipeline digest. Stages and adapters
|
|
therefore receive only concrete artifact maps.
|
|
|
|
The catalog retains sorted family member keys plus family/character/source
|
|
origins and the typed dependency/publish declarations for their later owners.
|
|
`member_dependencies` add corresponding ordinary concrete dependencies, while
|
|
the family-only `narratio.member_artifact.<family>` input form is rewritten to
|
|
the matching ordinary configured-artifact source. The catalog records those
|
|
resolved dependency and input identities with their declaring family and party
|
|
member. No member-artifact source is registered as a runtime policy source.
|
|
An enabled family publish declaration expands to ordinary configured-artifact
|
|
publish rules before the existing publish and lock validators run. Runtime
|
|
publication consequently receives no family wildcard or special matcher.
|