Reconcile sequence identity documentation

This commit is contained in:
2026-10-23 21:57:35 +00:00
parent d4133f23f5
commit e01aeca761
12 changed files with 239 additions and 260 deletions

View File

@@ -814,9 +814,10 @@ validated, but there are no imported peers to combine.
| Any other pair of present nodes | Any other pair of present nodes | `compose.conflict` |
Every non-mapping overlap is a conflict, even when values are equal, except for
sequence/sequence contributions at a registered sequence identity destination
or a source-declared collection destination. Nulls remain atomic conflicts.
Unregistered and undeclared sequences conflict when repeated and are never joined
sequence/sequence contributions at a destination with effective identity from an
application registration, a current YAML `key` declaration, or retained lower
policy. Nulls remain atomic conflicts.
Sequences without effective identity conflict when repeated and are never joined
by index. Disjoint entries under shared mappings compose, making stable keyed
mappings the natural representation for independently contributed entries.
@@ -827,44 +828,49 @@ segments created by `SequenceLiteralKey` and `SequenceAnyMappingKey`. A literal
`"*"` key is distinct from the wildcard constructor. Parsed numeric path tokens
remain literal mapping keys; explicit index segments and reserved `configkit` or
mapping-valued `extends` segments are invalid. The root selector is harmless under
the mapping-root schema. Each registration requires one or more literal field
names; compound field sets are normalized as sets, and field names are not
otherwise normalized.
the mapping-root schema. Selector segments consume one mapping level and match a
complete destination; an any-mapping-key segment matches exactly one key, not an
arbitrary prefix or suffix. Each registration requires one or more literal,
direct field names; compound field sets are normalized as sets, and field names
are not otherwise normalized. The same field-set and scalar-equality rules apply
to YAML `key` declarations.
At each selected path, records must provide every registered field as a scalar
string, number, boolean, or explicit null. The type-tagged tuple preserves
category distinctions, while equivalent finite decimal spellings such as `1`,
`1.0`, and `1e0` compare as the same number. Distinct tuples append in source and
list order; duplicate tuples conflict even when records are otherwise equal or
disjoint. Malformed records produce `compose.invalid`; duplicate identities
At each selected path, records must provide every effective identity field as a
scalar string, number, boolean, or explicit null. Field names form a set, so
order and repetition do not affect compound identity. The type-tagged tuple
preserves category distinctions: strings compare exactly, including empty
strings; numbers, booleans, and null remain distinct categories. Finite numbers
compare by exact mathematical value, so `1`, `1.0`, and `1e0` are equal, as are
`0` and `-0`, without floating-point rounding. Distinct tuples append in source
and list order; duplicate tuples conflict even when records are otherwise equal
or disjoint. Malformed records produce `compose.invalid`; duplicate identities
produce `compose.conflict`, with paths and source locations identifying a
duplicate without exposing identity values. Missing paths and non-sequence values
do not fail by themselves. Equivalent overlapping registrations coalesce;
incompatible overlaps and invalid registrations produce `options.invalid` before
do not fail by themselves. Equivalent overlapping registrations coalesce.
Incompatible overlaps and invalid registrations produce `options.invalid` before
source acquisition. Unused and root registrations are allowed and have no effect.
Registration controls strict composition and supplies identity fields for parent
lookup when surviving records in that sequence use `extends`. Dynamic selectors
apply at strict-composition and inheritance destinations without requiring the
application to enumerate mapping keys. Each underlay group and the main tier has
independent duplicate-identity state. Precedence assembly is unchanged: a present
higher-priority sequence replaces the lower sequence as one value, including at a
registered path.
Registrations provide identity for strict composition, sequence operations,
inheritance lookup, and final validation. Dynamic selectors apply at concrete
strict-composition, inheritance, and final-result destinations without requiring
the application to enumerate mapping keys. Each strict tier has independent
duplicate-identity state among its peers; layered precedence and inheritance
then apply the collection operation described below.
After effective secret lookup, the shared load lifecycle validates every matching
sequence in the final typed projection before application semantic validation.
This catches identities introduced by typed defaults, CLI assignment, or recorded
resolution, in both ordinary and layered loads. Only actual sequences are checked;
absent paths and other shapes remain the schema or application validator's concern.
Identity fields are read from effective typed payload regardless of authored
presence. Diagnostics use concrete collection and item paths and available source
locations, and never include identity or credential values. The guarantee ends at
successful publication; later caller mutations are outside it. The accepted
rationale is recorded in
[ADR 0014](adr/0014-sequence-placement-and-composition.md).
The [maintained prompt catalog](consumers/README.md#identified-prompt-sequences)
shows item path and glob imports, helper-supplied identity, and main-tier
replacement of a lower-priority sequence.
Final registration checks run in both ordinary and layered loads after effective
secret lookup and before application semantic validation. They examine actual
final typed sequences, including values introduced by defaults, CLI, or recorded
resolution; they do not require path existence or sequence shape. Diagnostics
use concrete collection and item paths and available source locations, without
exposing identity or credential values. The guarantee ends when loading publishes
successfully and does not cover later caller mutations.
YAML-only identity is enforced during structural composition, precedence, and
identified inheritance; it carries no final-result guarantee after typed
defaults, CLI, or application resolution. Applications that need that guarantee
register the identity constraint.
The [maintained catalog examples](consumers/README.md#identified-prompt-sequences)
show strict peers, precedence operations, inheritance, dynamic destinations, and
the distinction between YAML-only declarations and application registrations.
When an enabled source-version mode is selected, declarations are checked in each
source's own coordinates before placement and composition. Accepted non-main
headers are consumed before strict composition and cannot supply a registered
@@ -872,34 +878,74 @@ record identity. Final typed decoding and application validation still run on th
completed configuration.
Ordinary sources, layered inputs, and selected overlays may declare collection
behavior under `configkit.sequences` at the document root. Each entry has a required literal
`destination` and may specify `key` as one string or a nonempty list of strings,
plus a `mode` of `replace`, `append`, or `upsert`. These controls are removed
before application decoding. A declaration destination is relative to that
source's placement, so a mounted fragment's declaration is mounted with its
values. Declarations from
strict peers must agree on identity fields and on any explicitly supplied mode;
they apply even when the declaring source has no value at that destination.
Singleton sequence contributions are validated as well as peers. A key supplies
the identity used by `append` and `upsert`; without an explicit or retained mode,
a keyed collection uses `upsert`, while an unkeyed collection uses `replace`.
`append` keeps contribution order and rejects repeated identities. `upsert`
replaces a complete matched record at its existing position and appends new
identities in incoming order; it does not merge record fields.
behavior under `configkit.sequences` at the document root. Each entry has a
required literal `destination` and may specify `key` as one string or a nonempty
list of strings, plus a `mode` of `replace`, `append`, or `upsert`. These
controls are removed before application decoding. A declaration destination is
relative to that source's placement, so a mounted fragment's declaration is
mounted with its values. Declarations from strict peers must agree on identity
fields and on any explicitly supplied mode; omission expresses no mode
preference. They apply even when the declaring source has no value at that
destination: a key validates an existing retained sequence or remains pending
until records arrive, while a mode-only declaration affects later combinations
without recombining earlier tiers. A key supplies the identity used by `append`
and `upsert` and is checked against any application registration.
Strict peers at an identified destination first combine distinct records in
source and list order. Duplicate identities among peers or within one
contribution fail under every mode. After strict composition, precedence and
inheritance use one operation for the assembled incoming collection:
| Mode | Behavior at source-precedence and inheritance boundaries |
| --- | --- |
| `replace` | Replace the complete lower sequence with the assembled higher sequence. |
| `append` | Retain lower records and append higher records; duplicate identities fail. |
| `upsert` | Replace matching records in full at their lower positions, retain unmatched lower records, and append new identities. |
Upsert replaces the complete matched record; it does not recursively merge
record fields. Append and upsert require identity. With no explicit mode in the
current tier, an explicit lower mode persists; if none exists, effective identity
selects upsert and its absence selects replace. Inferred modes do not persist.
Explicit modes persist at their destination until changed by a higher explicit
mode, independently of YAML identity lifetime. A declaration alone does not
clear or recombine a collection.
The presence and value kind at a precedence or inheritance boundary determine
whether a collection changes:
| Higher contribution | `replace` | `append` / `upsert` |
| --- | --- | --- |
| Absent | Leave the lower value unchanged. | Leave the lower value unchanged. |
| Explicit `[]` | Clear the sequence. | Contribute no records. |
| Explicit null or another non-sequence value | Replace the lower value, subject to application schema. | Error; the operation requires a sequence. |
An absent lower value can be initialized by an incoming sequence under any mode.
A present lower null or other non-sequence value is not absence: append and
upsert cannot combine with it. An empty sequence still requires a valid mode and
identity where applicable. Null used as an identity component inside a record is
distinct from null used as the collection value.
Declarations govern ordinary strict source composition, including loads with
imports disabled, and each strict scope in layered loading. Layered groups and
main apply their resolved sequence operation between precedence tiers; a higher
strict scope inherits the lower identity and explicit mode before peer
composition. Overlay declarations apply after the base source graph is composed
and must agree with retained collection identity and any application
registration. During mapping-sibling inheritance, a parent's YAML identity can
follow its collection to the child destination, while the parent's explicit
operation stays at the parent destination. A child declaration selects the
operation at its destination; absent one, keyed collections use upsert and
unkeyed collections use replace. These policies do not change CLI behavior. A root
`configkit.imports` declaration still requires `Options.Imports`; `NoSource` has
no declaration scope.
imports disabled, and each strict scope in layered loading. Before composing a
higher tier's peers, ConfigKit makes applicable lower identity and explicit mode
available; after peer composition, the tier applies its resolved operation
against the lower collection. Selected overlays use the same collection policy
after the base source graph is composed. During mapping-sibling inheritance, a
parent's YAML identity can follow its collection to the child destination, while
the parent's explicit operation stays at the parent destination. The child
destination selects its operation; absent one, effective identity implies
upsert and its absence implies replace. Whole-sequence replacement discards
lower YAML identity and uses the replacing scope's key declaration, if any;
without one, the collection is unkeyed unless an application registration still
supplies identity. Replacement discards policies owned by removed records, while
an independently declared explicit mode and application registration remain
applicable. Item-local
policies follow their owning records: retaining a record retains its rules, while
whole-record upsert replacement discards the displaced record's rules. Matching
identity or reusing an index does not transfer them. These policies do not change
CLI behavior: CLI sequence assignments replace whole sequences after decoding.
A root `configkit.imports` declaration still requires `Options.Imports`;
`NoSource` has no declaration scope.
An empty mapping contributes no child definitions. Two empty mappings compose, and
an empty mapping neither clears nor conflicts with another mapping's children.
@@ -1076,6 +1122,12 @@ filenames unrestricted; a non-nil empty slice selects none. Defaults do not make
a field applicable to selectors that do not support it, and load calls snapshot
the supplied pointers and slice before source acquisition.
A source's `configkit.sequences` declarations apply independently of import
enablement; a `configkit.imports` declaration still requires `Options.Imports`.
Sequence identities can be supplied either by these YAML declarations or by
application registrations. The [strict composition contract](#strict-document-composition)
defines their agreement, operation, and lifetime rules.
Acquisition visits declarations in authored order and each declaration's selected
paths in namespace-path lexical order, preserving source-record and version-callback
order. Ordinary mapping contributions in a main tier are assembled dependency-first:
@@ -1087,9 +1139,10 @@ mapping scope, with helpers before the bodies that import them. Those local scop
do not use application collection policies and are not flattened into the outer
schedule. Item positions and diagnostic paths follow the resulting assembly order.
Each underlay group likewise acquires its inputs before dependency-ready assembly
in one strict scope. Independent groups remain separate precedence tiers; a higher
tier still replaces a lower tier's sequence values wholesale. Assembly order never
permits replacement.
in one strict scope. Independent groups remain separate precedence tiers; at a
declared or registered destination, the assembled higher sequence uses the
resolved collection operation against the lower sequence. Other sequences retain
whole-value replacement between tiers. Assembly order never permits replacement.
Acquisition order and resolution order serve different purposes: acquisition
assigns source records and callback order, while the shared scheduler determines
@@ -1120,13 +1173,17 @@ Each declaration may include `placement: mapping` or
including every match of a glob; placement is not inherited. Sequence-item
placement requires a nonempty effective destination and wraps the imported
mapping as one element of a sequence there. It does not split source sequences
or infer identity fields. Repeated sequences still conflict unless their path is
registered through `Definition.SequenceIdentities`; registration does not
change placement or precedence behavior. Sequence records retain source
locations through placement and history; indexes describe their assembled
positions while source coordinates continue to identify the declaring files.
Header/version policy and final application validation retain their existing
owners and timing; sequence registration is a strict-composition rule only.
or infer identity fields. Repeated sequences combine only when the destination
has an applicable YAML identity declaration or application registration; source
placement remains independent of collection identity and operation. Sequence
records retain source locations through placement and history; indexes describe
their assembled positions while source coordinates continue to identify the
declaring files.
Header/version policy retains its existing owner and timing. Application
registrations also enforce identity on the final effective typed configuration;
YAML-only declarations are enforced at structural combination and inheritance
boundaries. The [configuration contract](#strict-document-composition) defines
these validation boundaries.
An item import first opens a local mapping scope. Its body and ordinary helper
imports compose within that record. An omitted helper destination or