Consolidate the sequence roadmap around the settled design

This commit is contained in:
2026-10-23 00:11:50 +00:00
parent 58f4c39d6f
commit 844a15a3cb
2 changed files with 669 additions and 904 deletions

669
docs/roadmap/roadmap.md Normal file
View File

@@ -0,0 +1,669 @@
# Sequence Identity And Combination Roadmap
Status: settled feature roadmap, ready for a staged implementation plan. This
is the targeted end state, not an implemented contract. The subsequent plan will
specify execution order, the exact Go API, internal bookkeeping, and validation
stages.
## Purpose And Scope
Allow configuration authors to assemble collections from independently authored
records, declare identity in configuration, and progressively extend a baseline
collection through higher layers. Authors can replace the entire collection,
append distinct records, or replace complete records by identity while retaining
unmatched records. Sibling inheritance uses the same sequence operations and
identity-based defaults.
The motivating use case is a prompt catalog whose source-backed baseline supplies
records and whose application or installation layers add or supersede records.
Applications can optionally impose identity constraints that configuration must
respect and that hold in the final loaded configuration.
The feature covers ordinary and layered loading, imported items, inline
sequences, selected overlays, and sibling inheritance. Typed defaults, CLI
assignments, and application resolution retain their own combination semantics;
CLI application moves later in the loading lifecycle.
This scope is narrower than the deferred
[identified-record sequences proposal](future.md#1-identified-record-sequences).
Recursive merging of matching records and record-specific inheritance remain
separate candidates. The initial declaration surface is `configkit.sequences`;
`key` and `mode` shorthand on imports is deferred. Application lists remain
ordinary YAML sequences without control wrappers. Unkeyed sequence concatenation
is outside this feature.
## Current Baseline And Required Changes
The [import contract](../config.md#explicit-imports) owns existing selectors,
destinations, placement, and ordering. `placement: sequence-item` already places
each selected source mapping as one item at its effective destination. Item-local
helper imports can contribute fields before placement.
The [composition contract](../config.md#strict-document-composition),
[layered-loading contract](../config.md#layered-loading), and
[inheritance contract](../config.md#sibling-inheritance) describe the implemented
baseline:
| Area | Current behavior | Target behavior |
| --- | --- | --- |
| Identity declaration | `Definition.SequenceCompositions` registers one literal string field at each registered mapping path. | YAML collection declarations and optional authoritative application registrations support field sets and scalar identity components. |
| Strict composition | Registered sequences append distinct string identities and reject duplicates; repeated unregistered sequences conflict. | Collections with effective identity append distinct records and reject duplicates. Applicable lower-tier identity is available before composing higher-tier peers. Unidentified sequences retain strict-conflict behavior. |
| Source precedence and selected overlays | A present higher sequence replaces the lower sequence wholesale. | Collection policy selects `replace`, `append`, or `upsert`; overlays can declare policy for inline sequences. |
| Sibling inheritance | An inheriting mapping recursively overlays its resolved parent; supplied sequences replace wholesale. | Mappings retain recursive combination, while sequences use destination policy and the same three operations. |
| CLI overrides | Apply before inheritance and typed defaults/decoding. | Apply after inheritance and typed defaults/decoding, before application resolution. |
| Identity enforcement | Registration governs strict sequence composition. | Structural identity checks cover identified combinations; application registrations additionally constrain final effective sequences. |
Source-backed underlays participate in source assembly. `Definition.Defaults`
provides typed defaults later; extending source combination does not turn typed
default lists into source underlays.
## Collection Declarations
Use a source-root `configkit.sequences` block containing collection declarations.
Each entry identifies a `destination` and may declare `key`, `mode`, or both.
Identity and mode can be supplied independently. This form serves imported items,
inline records, ordinary mapping imports, and inline sequences in selected
overlays.
```yaml
configkit:
sequences:
- destination: [prompts]
key: [namespace, id]
mode: append
imports:
- dir: prompts
pattern: '*.yaml'
destination: [prompts]
placement: sequence-item
```
Each selected file contributes a record such as:
```yaml
namespace: support
id: welcome
text: Welcome
```
Here append retains a lower-layer collection and adds distinct higher-layer
records. Identity and combination controls belong to the collection declaration,
so imported and inline contributions share one policy surface.
### Destinations And Scope
`destination` uses the existing sequence of literal mapping keys. Resolve it
relative to the declaring source's placement: a fragment mounted under `catalog`
can declare `[prompts]` locally. A sequence-item source can declare a nested
collection within its own item without referring to an eventual numeric index.
Preserve source and item-local scope boundaries.
A collection's scope is its resolved destination within its composition scope.
Resolve relative destinations before reconciling declarations. Different imported
items retain separate scopes for their nested collections. A declaration governs
all contributions to its collection in the tier, including imported and inline
records; it does not govern only records in the declaring file.
Gather declarations before composing the relevant scope so their effect does not
depend on which contribution is assembled first. Retain applicable controls
through source precedence and inheritance, then keep them out of application
decoding.
Sequence declarations are independent of filesystem import enablement. Selected
overlays may contain `configkit.sequences`, extending the current blanket
prohibition on overlay `configkit` declarations. This does not enable imports
in overlays.
### Overlay Authoring
For an existing sequence `[foo, bar]`, an overlay can replace `foo` in full:
```yaml
configkit:
sequences:
- destination: [prompts]
key: id
mode: upsert
prompts:
- id: foo
text: Revised definition
```
The result is `[foo*, bar]`. The explicit upsert mode can be omitted when no
different explicit mode applies from a lower tier: effective identity then
selects upsert. To add a distinct record instead:
```yaml
configkit:
sequences:
- destination: [prompts]
key: id
mode: append
prompts:
- id: baz
text: Another prompt
```
The result is `[foo, bar, baz]`. To replace the whole sequence:
```yaml
configkit:
sequences:
- destination: [prompts]
mode: replace
prompts:
- id: bar
text: Bar definition
- id: baz
text: Baz definition
```
The result is `[bar, baz]`. Explicitly selecting the default operation is valid.
With no applicable explicit mode, omitting this declaration selects replace for
an unidentified collection and upsert for an identified one. A retained explicit
mode continues to govern.
## Identity Definitions And Constraints
### Field Sets
`key` accepts a string or a nonempty list of strings naming direct, literal
fields in each record. A name such as `metadata.id` denotes that literal field,
not a nested lookup. Treat field names as a set: remove repeated names and
ignore authored order when comparing definitions.
Thus `id`, `[id]`, and `[id, id]` are equivalent; `[namespace, id]`,
`[id, namespace]`, and `[namespace, id, id]` are also equivalent. An empty list
is invalid because it supplies no identity fields. These rules apply to both
configuration declarations and application registrations.
Use a consistent field order internally to construct identity tuples. Preserve
component types and tuple boundaries so unstructured concatenation cannot cause
otherwise distinct identities to collide.
### Scalar Components And Equality
Each identified record must be a mapping with every designated identity field
present. Components may be strings, numbers, booleans, or explicit null. Missing
fields and mapping or sequence components are invalid. Present null differs
from absence; two single-field null identities are equal. Compound identities
are equal when all corresponding components are equal.
Strings compare exactly, including empty strings, without trimming, case folding,
or normalization. Scalar categories remain distinct: `"1"`, `1`, `true`, and
null are different identities.
Numbers compare by mathematical value across integer, fractional, and exponent
spellings: `1`, `1.0`, and `1e0` are equal, as are `0` and `-0`. Use exact numeric
normalization for identity comparison without rounding through a floating-point
conversion. This does not rewrite authored values or change existing typed
decoding and projection semantics. The parser's rejection of non-finite numeric
forms remains unchanged.
### Application-Owned Constraints
Retain an optional application interface for registering collections and imposing
their identity definitions. Configuration may define identity where no application
constraint exists. When a declaration omits `key`, an applicable registration
supplies identity. An explicit equivalent definition is accepted, and a different
definition fails under every mode, including replace.
For example, if the application requires `id`, `key: [id, name]` is invalid:
that compound identity would allow repeated `id` values with different names.
A registration is an authoritative constraint, not an omission-filling default.
Copying a collection through inheritance does not create another application
registration; registrations constrain their registered destinations.
Identity constraints validate actual sequences. They do not independently require
a path to exist, be non-null, or have sequence shape. The application schema and
validation own those requirements. Final-result enforcement for application
registrations is defined under [Validation And Diagnostics](#validation-and-diagnostics).
## Strict Composition And Precedence Operations
### Compose Peers Before Applying Precedence
Resolve applicable identity and mode policy, strictly compose the tier's peer
contributions, then apply one selected operation per collection against the
accumulated lower value. The tier operation does not sequentially replace peer
source contributions. Selected overlay contributions use the same combination
semantics.
Within a strict tier, equivalent identity definitions coalesce and different
field sets fail. Agreeing explicit modes coalesce; conflicting explicit modes
fail. Omission expresses no preference. A root declaration or a later declaration
has no precedence over conflicting peers. Explicitly selecting a default mode
is still a real instruction and can conflict with another explicit mode.
Identified peer sequences append distinct records. Duplicate identities among
peers or within a contributed list fail under all three modes, including replace
and upsert. Equal or disjoint record payloads do not excuse a duplicate identity.
Unidentified sequences retain their current strict-composition conflict behavior.
For example, higher-tier fragments `[C]` and `[D]` with an agreed identity assemble
as `[C, D]`. Against lower `[A, B]`, append or upsert produces `[A, B, C, D]`,
while replace produces `[C, D]`. Peers `[B]` and `[B*]` conflict even under
upsert because neither has higher precedence.
### Available Operations
Use one spelling for each mode: `replace`, `append`, and `upsert`.
| 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, retain unmatched lower records, and append new identities. |
Append and upsert require effective identity. ConfigKit does not infer identity
from an `id` field or record position. Whole-sequence replacement does not require
identity agreement with lower YAML policy, but application constraints still apply.
Letters denote record identities; `B*` has the same identity as `B` with a different
payload:
| Lower | Higher | Mode | Result |
| --- | --- | --- | --- |
| `[A, B]` | `[C]` | `append` | `[A, B, C]` |
| `[A, B]` | `[C]` | `replace` | `[C]` |
| `[A, B]` | `[B*]` | `append` | Duplicate-identity error |
| `[A, B]` | `[B*]` | `replace` | `[B*]` |
| `[A, B]` | `[B*]` | `upsert` | `[A, B*]` |
| `[A, B]` | `[B*, C]` | `upsert` | `[A, B*, C]` |
Upsert replaces the whole matched record. If lower `B` has `id`, `text`, and
`temperature`, while higher `B` has only `id` and `text`, the result at this
boundary has only `id` and `text`. Subsequent defaulting or other lifecycle
operations retain their own semantics. Mappings continue to combine recursively;
matching sequence records do not recursively merge fields.
### Mode Selection And Persistence
Select the operation in this order:
1. Reconcile explicit modes within the current tier; conflicting peers fail.
2. Use the current tier's explicit mode if supplied.
3. Otherwise retain an applicable explicit mode from a lower tier.
4. With no explicit mode, infer upsert when effective identity is available.
5. Otherwise infer replace.
Effective identity can come from an application registration, current declarations,
or applicable lower policy, subject to the identity agreement and lifetime rules
below. Determine the inferred mode from available identity before applying the
operation. A plain higher-tier sequence can therefore use upsert without repeating
a key declaration.
An explicit mode persists at its applicable destination until a higher-priority
explicit declaration changes it. New sequence values alone do not reset it, and
an unrelated overlay leaves it intact. A mode-only declaration can change policy
without supplying records; it neither clears the collection nor retroactively
recombines earlier tiers. Later source contributions and inheritance use the
resulting policy.
| Lower explicit mode | Higher declaration | Effective mode |
| --- | --- | --- |
| `append` | Mode omitted | `append` |
| `append` | `upsert` | `upsert` |
| `upsert` | `replace` | `replace` |
| None | Mode omitted, effective identity available | Inferred `upsert` |
| None | Mode omitted, no effective identity | Inferred `replace` |
Only explicit modes persist. Inferred defaults are recomputed from effective
identity when needed. Explicit replace continues to apply in subsequent tiers
until another mode is declared; introducing identity alone does not override it.
Mode persistence does not preserve YAML identity discarded by replacement, nor
policies owned by discarded records. Source precedence and inheritance use the
same destination policy, without separate per-phase settings. CLI assignments
remain outside mode selection.
### Presence, Empty Sequences, Null, And Kind Mismatches
At a collection's precedence boundary:
| 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 | Ordinary replacement, subject to application schema. | Error: the operation requires a sequence. |
A declaration alone does not clear a collection. An optional import selecting
nothing contributes nothing and does not erase existing records. An absent lower
collection can be initialized by an incoming sequence under any mode, subject to
identity requirements. Explicit lower null or another non-sequence value is not
absence: append and upsert fail against it, while replacement remains available.
An empty incoming sequence bypasses neither kind checks nor identity requirements.
With effective identity and no explicit mode, `prompts: []` uses upsert and does
not clear the collection. Clearing requires replace, selected in the current tier
or retained from lower policy. Replacing an identified collection with null also
requires replace and a schema permitting null. Collection null is distinct from
a present null identity component inside a record.
### Ordering
Retain existing dependency-ready source scheduling within each tier and authored
item order within lists. Gathering declarations does not change assembly order;
identity values do not sort records.
- Append keeps lower records in order, followed by higher records in assembled order.
- Replace keeps only higher records in their assembled order.
- Upsert keeps a matched record's lower position and appends new identities in
higher-tier order.
For example, lower `[A, B, C]` and higher `[C*, A*, D]` produce `[A*, B, C*, D]`
under upsert. Whole-sequence replacement gives the higher tier control of the
complete sequence order.
## Policy Agreement, Lifetime, And Ownership
### Identity Agreement And Replacement
Append and upsert use one agreed identity for all participating records:
| Lower identity | Higher declaration | Result when combining records |
| --- | --- | --- |
| `id` | Omitted | Use `id`. |
| `id` | `id`, `[id]`, or `[id, id]` | Accept the equivalent definition. |
| `id` | `[namespace, id]` | Error: different definitions. |
| None | `id` | Establish `id` and validate all participating records. |
Introducing identity for an unkeyed lower sequence requires checking both lower
and incoming records, including uniqueness within each contribution. With no
identity from the application or either participating collection, append and
upsert fail.
YAML identity follows the collection that survives an operation:
| Operation | Effective YAML identity |
| --- | --- |
| Append or upsert | Retain or establish the agreed identity. |
| Whole-sequence replacement | Discard lower YAML identity and use the replacing tier's identity declaration, if any. |
| Replacement by null or another non-sequence value | Discard lower YAML identity and retain any identity explicitly declared by the replacing tier. |
Application constraints remain authoritative under every operation. Without an
application constraint, replacement can introduce a different identity, such as
changing from `id` to `name`. Replacing a sequence without a new key declaration
or application registration produces an unkeyed sequence. No identity-removal
directive is needed.
The reset occurs only when the collection value is actually replaced. Declaring
replace without supplying a replacement does not authorize changing the identity
of retained records. Explicit mode lifetime is separate from YAML identity
lifetime.
### Declarations Without Sequence Values
A key declaration without accompanying records applies immediately to an existing
retained sequence: check agreement and validate its records, without recombining
earlier tiers. For example, a higher tier that only declares
`destination: [prompts], key: id` identifies and validates the retained lower
`prompts` sequence.
If no collection exists, retain the declaration in its owning scope for later
contributions. An otherwise empty underlay can establish identity for main-tier
fragments. An explicit identity declaration can also remain pending at a
non-sequence destination; it does not independently impose a shape requirement.
The presence and kind rules still determine whether an attempted operation fails.
Item-local declarations retain their ownership boundaries.
### Lower-Tier Identity During Strict Composition
Resolve applicable identity before composing a higher tier's peers, using
application registrations, available lower-tier policy, and declarations gathered
within the higher tier's scope. Authors need not repeat a baseline key solely
because the higher tier has multiple fragments.
For example, an underlay establishes `key: id` for `prompts`, and two main-tier
fragments contribute `[foo]` and `[bar]` without a key declaration. They strictly
compose as `[foo, bar]` before the main tier combines with the accumulated lower
collection. If both contribute `foo`, composition fails; upsert does not give
precedence to peers.
Lower YAML identity remains subject to agreement, replacement, and ownership.
A replacing collection must not be constrained by obsolete lower identity just
because policy resolution precedes composition. Likewise, a replacement record
cannot borrow nested identity owned by the discarded lower record, even when
outer identity or numeric position matches. Application constraints still apply.
Higher-tier declarations do not retroactively repair invalid lower-tier composition.
### Nested Record Ownership
Attach item-local policies to their owning contributions so they follow surviving
records independently of numeric indexes. Whole-record upsert discards the old
record's local policies along with its fields. Whole-sequence replacement discards
policies owned by removed records. A replacement record supplies its own local
declarations; equal outer identity or final index does not transfer discarded
policies.
For example, a source imported as one record into `prompts` can declare:
```yaml
configkit:
sequences:
- destination: [steps]
key: id
mode: append
id: foo
steps:
- id: prepare
```
The declaration governs `steps` inside that `foo` record. Retaining `foo` retains
its local policy, including the explicit mode. Upserting a new `foo` discards the
old `steps` and its declaration even though the outer record keeps its position.
Policies independently applicable at a destination are not discarded merely
because an individual record is replaced. Ownership determines which declarations
are removed. Application registrations remain constraints at their registered
destinations. Inheritance copies transfer identity and choose operations as
specified next.
## Sibling Inheritance
Complete source-tier assembly before resolving inheritance. Retain the existing
dependency-ready algorithm and sibling scope rules. At each inheritance combination,
the resolved parent's collection is the lower value and the inheriting mapping's
local collection is the higher value. Mappings combine recursively; identified
sequence records remain whole-record replacements.
Use the inheriting destination collection's policy: an applicable explicit mode,
including one retained through source tiers, wins. Otherwise infer upsert with
effective identity and replace without it. There is no separate default-append
rule for inheritance. The same destination policy governs source-precedence and
inheritance combinations.
YAML identity follows a collection copied through inheritance, subject to agreement
and replacement rules. A source collection's mode does not automatically become an
instruction at a different inheriting destination. For example, source `steps`
has `key: id` and `mode: append`; an inheriting mapping supplies local `steps`
without an applicable explicit mode. The copied identity selects upsert there.
An explicit destination append instead selects append. A matching destination key
agrees; a different key conflicts when combining records, while explicit replace
can establish new YAML identity subject to application constraints.
For example:
```yaml
configkit:
sequences:
- destination: [parent, items]
key: id
child:
items:
- id: foo
value: A
- id: bar
value: B
parent:
extends: child
items:
- id: baz
value: C
```
Despite the names, `parent` inherits from `child`. Identity at `[parent, items]`
selects upsert, producing `[foo, bar, baz]`. If the local sequence instead supplies
a revised `foo`, the result is `[foo*, bar]`. Append rejects that repeated identity;
replace retains only the local sequence.
The same ordering, presence, and kind rules apply at inheritance boundaries.
An omitted local sequence retains the inherited sequence. Explicit `[]` clears it
under replace and contributes no records under append or upsert. Retain controls
until inheritance uses them, then exclude them from application decoding.
## Loading Lifecycle
Use the following order:
1. Assemble source tiers and selected overlays, resolving policy before each
tier's strict composition and applying collection operations at precedence
boundaries.
2. Resolve sibling inheritance, then apply typed defaults and decode the assembled
configuration.
3. Apply explicit CLI overrides to that configuration and check supplied values
against the application schema.
4. Run application resolution against the overridden configuration.
5. Resolve effective secret references, validate the final configuration including
application-registered identities, and publish it.
### Typed Defaults And Application Resolution
Source-backed underlays, including application-supplied mapping sources,
participate in sequence combination. Typed `Definition.Defaults` remains the
initial decoded state: supplied sequences replace default sequences. Source
append does not append to a typed default list.
Application resolution retains assignment and recorded-transformation semantics.
ConfigKit does not automatically combine a callback's assigned sequence with its
previous value. Application-registered identities receive final validation
independently of how defaults, CLI overrides, or callbacks produce their values.
### CLI Precedence
CLI overrides are the highest-precedence input to application resolution.
Supplied sequences replace whole sequences, including sequences inside supplied
mappings. Mappings retain recursive override behavior; empty sequences and
permitted nulls retain explicit replacement meaning. Configuration append or
upsert does not reinterpret CLI assignments. Inheritance and defaulting do not
run again afterward: overriding assembled `[A, B]` with `[C]` presents `[C]` to
application resolution without inheritance reintroducing prior records.
Application resolution sees CLI values and their explicit-presence and source
metadata. `Resolution.Default` preserves explicit CLI values; normalization and
recorded application transformations may still change them. For example, a derived
URL can use a CLI hostname, while an explicit CLI URL can be preserved by an
absence-based default. Retain recording and provenance requirements for those
transformations.
Reject structural directives such as `extends` and `configkit` in CLI override
paths or supplied values. CLI input supplies application values after structural
processing; it cannot supply or cancel inheritance directives, change completed
relationships, or prevent earlier inheritance failures.
Typed decoding also precedes CLI application, so a CLI replacement cannot repair
an earlier decoding failure. A source string in an integer field fails before a
valid CLI integer can apply. This changes current override-before-decoding
behavior. Semantic validation remains after overrides and application resolution.
Secret lookup uses effective references after overrides and resolution. Neither
stage bypasses final application identity constraints or semantic validation.
## Validation And Diagnostics
### Structural Identity Checks
Allow item-local helper imports to complete and consume applicable non-main
source-version headers before checking payload identity for collection assembly.
A helper can supply an identity component; a consumed header cannot supply one
after removal.
Validate each identified contribution before combination and check uniqueness in
the resulting identified collection, including inheritance operations. Upsert
matches across precedence are replacements; duplicates within a contribution
remain errors. Later inheritance, typed defaults, or callbacks cannot repair an
identity missing at an earlier assembly boundary.
YAML-only identities are enforced during structural composition, precedence
assembly, and identified inheritance operations. They have no final-result
guarantee after typed defaulting, CLI overrides, or application resolution. Those
stages can produce duplicate identities or otherwise violate a YAML-only identity.
Applications needing a final guarantee register the constraint through the
application-owned interface.
### Final Application Constraints
On successful loading, final effective collections covered by application
registrations satisfy their declared identity constraints. Retain assembly-time
checks and validate final effective sequences before publication, after defaults,
inheritance, overrides, application resolution, and secret lookup.
This guarantee covers actual sequences, not path existence or allowed shape.
It does not require every intermediate callback state to satisfy the constraint
and does not cover caller mutations after loading returns. Final checks examine
effective typed values; assembly checks validate authored contributions. A final
scalar alone does not prove that a source explicitly supplied that field.
Use the existing canonical final-value representation and shared publication
path as the foundation. The guarantee requires neither a new mutation-history
system nor exact causal reconstruction for records supplied by defaults or
callbacks.
### Diagnostics And Inspection
Identity diagnostics identify collection and item paths using available origin
information without exposing identity values. Conflicting declarations identify
the collection destination and declaration locations.
Correctness includes final values, projected item paths, source destinations,
provenance, history, sensitivity, and passive resource contexts. Appending can
change final indexes; replacing changes which contributions survive. Preserve
the existing loading and inspection contracts through both operations, including
policy ownership and inheritance transfer.
## Work Needed From The Baseline
The implementation plan should cover these connected changes, with execution
order and test sequencing defined there:
- **Declarations and public API:** extend application registration to the settled
identity model; parse and consume `configkit.sequences` across supported source
forms and selected overlays; preserve placement-relative and item-local scopes.
Keep sequence declarations independent of filesystem import enablement.
- **Identity and policy representation:** implement field-set comparison, typed
scalar identity tuples with exact numeric equality, declaration agreement,
pending declarations, explicit-mode persistence, replacement lifetime, and
record-owned policy transfer. Choose bookkeeping proportional to these semantics.
- **Assembly handoffs:** make accumulated lower policy available before higher-tier
strict composition. Separate source acquisition from policy-aware composition
or otherwise revise assembly sequencing; current main-before-underlay composition
is not a design constraint. Preserve distinctions among acquisition order,
dependency-ready scheduling within tiers, and precedence order. Account for
source records, callbacks, main-version checks, and failure diagnostics. Retain
version ownership and validation semantics; higher replacement cannot hide
lower-tier structural failures.
- **Combination and inheritance:** implement the three operations with strict peer
handling, whole-record replacement, presence rules, stable ordering, and the
destination policy rules for inheritance. Preserve provenance, history,
sensitivity, paths, and resource contexts as records survive, move, or disappear.
- **Lifecycle and validation:** move CLI application after inheritance and typed
defaults/decoding, preserve explicit-presence and source metadata for application
resolution, reject CLI structural directives, and add final application identity
validation through the shared publication path. Keep YAML-only enforcement
within structural operations.
- **Documentation and verification:** exercise ordinary and layered loading,
imported and inline collections, item-local scopes, version-header consumption,
selected overlays, inheritance, typed defaults, CLI, callbacks, and inspection.
Update the public contract and consumer examples when behavior lands.
Before implementation, record the durable architectural changes in a superseding
decision and reconcile affected architecture policy. The feature changes identity
ownership and precedence replacement in
[ADR 0014](../adr/0014-sequence-placement-and-composition.md), and sequence
combination and CLI-before-inheritance ordering in
[ADR 0009](../adr/0009-sibling-inheritance.md). Existing ADRs and contracts remain
authoritative until that architectural and implementation work lands. This roadmap
does not select migration machinery.
No open feature decisions remain. The implementation plan should translate this
end state into reviewable stages without adding new guarantees, restrictions, or
combination semantics.

View File

@@ -1,904 +0,0 @@
# Sequence Identity And Combination
Status: feature roadmap with OD-1 through OD-12 settled and ready for a staged
implementation plan. No open design decisions remain in this roadmap. This is
not an implementation plan or an implemented contract.
## Motivation
Allow configuration authors to assemble collections from independently authored
records, declare their identity in configuration, and progressively extend a
baseline collection through higher layers. Authors should also be able to
replace the accumulated collection or replace individual records by identity.
The motivating example is a prompt collection whose defaults supply some
records and whose application or installation layers add or supersede records.
Across tiers, authors need three operations: whole-sequence replacement,
duplicate-rejecting append, and whole-record replacement by identity. The latter
retains unmatched lower-tier records and adds higher-tier records with new
identities. Matching records are replaced in full. Sibling inheritance through
`extends` uses the same sequence operations and effective-identity defaults.
## Current Baseline
The [import contract](../config.md#explicit-imports) owns current selector,
destination, placement, and ordering behavior. An import with
`placement: sequence-item` places each selected source mapping as one item at
its effective destination. Item-local helper imports can contribute fields
before placement.
The application currently registers sequence identity through
`Definition.SequenceCompositions`, using one literal string field at each
registered mapping path. Within a strict tier, distinct identities append and
duplicate identities fail. Repeated unregistered sequences conflict. Across
precedence tiers, a higher sequence replaces the lower sequence wholesale.
The [composition contract](../config.md#strict-document-composition) owns these
rules; [layered loading](../config.md#layered-loading) owns tier precedence.
Current [sibling inheritance](../config.md#sibling-inheritance) recursively
overlays an inheriting mapping onto its resolved parent and replaces supplied
sequences wholesale. The proposed inheritance extension is settled in OD-7.
Source-backed underlays and `Definition.Defaults` have different lifecycle
roles. Underlays participate in source assembly; typed defaults are applied
later. Extending one does not automatically imply extending the other.
## Requested Capabilities
The proposed `configkit.sequences` block introduces a `key` declaration that
accepts either one field or a list of fields for a compound identity.
Fields are treated as a set; identity components may be strings, numbers,
booleans, or explicitly supplied null values.
Applications retain an optional interface for registering a collection
and imposing its identity definition. Configuration declarations must conform
to an application-owned constraint when one exists. The exact Go interface
remains to be designed.
```yaml
configkit:
sequences:
- destination: [prompts]
key: [namespace, id]
mode: append
imports:
- dir: prompts
pattern: '*.yaml'
destination: [prompts]
placement: sequence-item
```
Each selected file would contribute a record such as:
```yaml
namespace: support
id: welcome
text: Welcome
```
Here `mode: append` illustrates retaining a lower-layer collection and adding
distinct records from the higher layer. The `mode` field expresses the operation;
OD-5 defines its names and effective-identity default. Identity and combination
controls belong to the collection declaration; import shorthand is deferred.
Letters below denote record identities; `B*` is a higher-tier record with the same
identity as `B` and a different payload. The outcomes and ordering shown are
settled below.
| Lower-tier sequence | Higher-tier sequence | Operation | Proposed result |
| --- | --- | --- | --- |
| `[A, B]` | `[C]` | `append` | `[A, B, C]` |
| `[A, B]` | `[C]` | `replace` | `[C]` |
| `[A, B]` | `[B*]` | `append` | Duplicate-identity error |
| `[A, B]` | `[B*]` | `replace` | `[B*]` |
| `[A, B]` | `[B*]` | `upsert` | `[A, B*]` |
| `[A, B]` | `[B*, C]` | `upsert` | `[A, B*, C]` |
Whole-sequence replacement discards the lower sequence. A repeated identity in the
retained combined sequence is an error under the proposed append semantics,
including when the records have identical or disjoint payloads.
## Settled Design Choices
### Collection Declarations
**OD-2: settled.**
Use a root `configkit.sequences` block containing a sequence of collection
declarations. Each entry identifies a `destination` and may declare `key`,
`mode`, or both. Application sequences remain ordinary YAML data. This declaration
form serves imported items, inline records, and sequences supplied by ordinary
mapping imports, including inline sequences in selected overlays.
`destination` uses the existing sequence of literal mapping keys. Resolve it
relative to the declaring source's placement: a fragment mounted under
`catalog` can declare `[prompts]` locally. A sequence-item source can declare a
nested collection within its own item without referring to an eventual numeric
index. Preserve those source and item-local scope boundaries.
OD-11 settles policy ownership when enclosing records are replaced or inherited.
Identity and mode can be supplied independently. Omitted identity can use an
application registration; an explicit matching declaration is accepted, and a
conflicting declaration is invalid. Whole-sequence replacement does not itself
require identity matching, but application-owned constraints still apply.
Identity-policy lifetime across tiers is settled in OD-3 below.
Mode describes the collection's combination instruction at source-precedence
and inheritance boundaries, as detailed in OD-7. An explicit mode persists at
the same applicable destination until a higher-tier declaration changes it.
Gather collection declarations before composing their relevant scope so their
effect does not depend on which contribution is assembled first. Identity agreement is settled
in OD-3; operation agreement and omission among peers are settled in OD-4.
OD-9 settles how operation controls survive source precedence for later inheritance.
Permit sequence declarations in selected overlays and make their use independent
of filesystem import enablement. This deliberately changes the current blanket
prohibition on overlay `configkit` declarations. Enabling imports in overlays
is not part of this decision. Consume sequence controls before application
decoding, as with other structural directives.
For example, an overlay can replace a complete record by identity:
```yaml
configkit:
sequences:
- destination: [prompts]
key: id
mode: upsert
prompts:
- id: foo
text: Revised definition
```
The explicit `mode: upsert` line can be omitted here when no different explicit
mode applies from a lower tier: `key: id` then supplies the identity for inferred
upsert. An overlay can instead select duplicate-rejecting append:
```yaml
configkit:
sequences:
- destination: [prompts]
key: id
mode: append
prompts:
- id: baz
text: Another prompt
```
Or it can explicitly select whole-sequence replacement:
```yaml
configkit:
sequences:
- destination: [prompts]
mode: replace
prompts:
- id: bar
text: Bar definition
- id: baz
text: Baz definition
```
Explicitly selecting the default operation is valid. With no applicable explicit
mode, removing the last example's `configkit` block selects replace only when no
effective identity is available; an identified collection instead selects upsert.
A retained explicit mode also affects omission. Explicit `mode: replace` overrides
either another retained mode or inferred upsert. OD-5 defines the selection rule
for `replace`, `append`, and `upsert`.
Start with this common collection declaration and defer `key` and `mode`
shorthand on imports. A single declaration form covers inline overlays and mixed
inline/imported collections without duplicate authoring surfaces. Local wrappers
around sequence values would require distinguishing application data from
control mappings; the existing reserved namespace keeps that distinction clear.
### Identity Definitions And Lifetime
**OD-3: settled.**
#### Field Sets
`key` accepts a string or a nonempty list of strings naming direct, literal
fields in each record. `key: id` and `key: [id]` are equivalent. A name such as
`metadata.id` denotes that literal field, not a nested lookup.
Treat the selected fields as a set: remove repeated names and disregard authored
field order when comparing definitions. For example, `[namespace, id]`,
`[id, namespace]`, and `[namespace, id, id]` define the same identity. An empty
list is invalid because it supplies no identity fields. These equivalence rules
also apply when comparing configuration declarations with application
registrations.
Use a consistent field order internally to construct compound identity tuples.
Preserve component types and tuple boundaries; concatenating unstructured values
must not cause otherwise distinct identities to collide.
#### Scalar Components And Equality
Each identified record must be a mapping, and every designated identity field
must be present. Components may be strings, numbers, booleans, or explicit null.
Missing fields and mapping or sequence components are invalid. A present null is
a valid component and is distinct from an absent field. Two single-field null
identities are equal; in a compound identity, all components must compare equal.
Strings compare exactly, with no trimming, case folding, or normalization.
Empty strings remain valid. Scalar categories remain distinct: the string
`"1"`, the number `1`, the boolean `true`, and null are different identities.
Numbers compare by mathematical value across integer, fractional, and exponent
spellings: `1`, `1.0`, and `1e0` are equal, as are `0` and `-0`. Use exact numeric
normalization for identity comparison without rounding through a floating-point
conversion. This comparison does not rewrite authored values or alter existing
typed decoding and projection semantics. The existing parser's rejection of
non-finite numeric forms remains unchanged.
#### Agreement And Introduction
Within a strict tier, identity declarations for the same collection must agree.
Equivalent field sets coalesce; different sets produce an error. Across tiers,
append and replacement by identity use one agreed definition for the records
being combined:
| Lower collection identity | Higher declaration | Result when combining records |
| --- | --- | --- |
| `id` | Omitted | Use `id` |
| `id` | `id`, `[id]`, or `[id, id]` | Accept the equivalent definition |
| `id` | `[namespace, id]` | Error: different identity definitions |
| None | `id` | Establish `id` and validate all participating records under it |
OD-10 makes applicable lower-tier YAML identity available during the higher
tier's strict composition, before this precedence combination occurs.
An application registration supplies identity when configuration omits it and
remains authoritative over explicit declarations. If neither the application nor
either participating collection supplies an identity, append and replacement by
identity fail; ConfigKit does not infer an `id` convention. Introducing identity
for a previously unkeyed lower sequence requires checking both lower and incoming
records, including uniqueness within each contribution.
#### Replacement And Policy Lifetime
YAML-owned identity follows the collection that survives an operation. OD-12
also permits declarations pending at absent or non-sequence destinations.
| Operation | Effective YAML-owned identity |
| --- | --- |
| Append | Retain or establish the agreed identity |
| Replacement by identity | Retain or establish the agreed identity |
| Whole-sequence replacement | Use the replacing tier's declaration and discard the lower collection's YAML-only identity |
| Replacement by null or another non-sequence value | Discard the lower YAML-only identity; retain any identity explicitly declared by the replacing tier under OD-12 |
Without an application constraint, whole-sequence replacement may introduce a
different identity, such as replacing an `id`-keyed collection with a `name`-keyed
collection. A whole-sequence replacement with no new identity declaration and no
application registration produces an unkeyed sequence. No separate identity
removal directive is needed.
Application registrations continue to constrain the replacement collection.
If the application requires `id`, declaring `name` is invalid under every mode.
The policy reset above applies when the collection value is actually replaced.
OD-6 defines the value effects of absence, null, and kind mismatches; OD-12
defines the corresponding declaration-only and identity-lifetime rules.
YAML-only identity declarations receive assembly-time enforcement, as settled
in OD-1 below.
### Combination Scope, Declarations, And Ordering
**OD-4: settled.**
#### One Operation Per Collection Per Tier
Resolve applicable collection policy, including available lower-tier identity
under OD-10, before strictly composing a tier's contributions. Then apply one
selected operation per collection against the accumulated lower sequence. A collection declaration
governs all contributions to that collection in its tier, including imported and
inline records; it does not govern only records in the declaring file.
Collection scope is the resolved destination within its composition scope.
Resolve relative destinations first and preserve separate item-local scopes for
collections inside different imported items. Apply the same combination
semantics to selected overlay contributions.
For example, with an agreed identity, higher-tier fragments contributing `[C]`
and `[D]` assemble `[C, D]`. Against lower `[A, B]`, append or replacement by
identity produces `[A, B, C, D]`; whole-sequence replacement produces `[C, D]`.
The tier operation does not sequentially replace peer source contributions.
#### Operation Agreement And Omission
Coalesce agreeing explicit modes for the same collection within a tier and
reject conflicting explicit modes. Omission expresses no preference; apply the
effective-identity default only after reconciling declarations and checking for
an applicable explicit mode retained from a lower tier.
| Modes declared within one tier | Effective operation |
| --- | --- |
| `append` and `append` | `append` |
| `append` and omitted | `append` |
| `replace` and omitted | `replace` |
| `append` and `replace` | Error |
| All omitted | Retain an applicable lower explicit mode; otherwise infer from effective identity |
Explicitly selecting the default is a real instruction and can conflict with
another explicit selection. Different modes in different tiers are valid; the
higher tier's explicit mode replaces the lower mode, while omission retains it.
Neither a root declaration nor a later declaration wins over conflicting peers.
Diagnostics identify the collection
destination and conflicting declaration locations. OD-5 defines mode names and
the default-selection rule.
#### Strict Duplicate Handling
Duplicate identities among peer contributions or within one contributed list
remain strict-composition errors under all three modes. For example, peer
fragments contributing `[B]` and `[B*]` conflict even under replacement by
identity: neither has higher precedence. Lower `[B]` and higher `[B*]` can
instead produce `[B*]` at a precedence boundary. Whole-sequence replacement
does not permit duplicate identities within an identified higher-tier sequence.
#### Ordering
Retain existing dependency-ready source scheduling within each tier and authored
item order within each list. Gathering collection declarations does not change
assembly order, and identity values do not sort records.
Across tiers:
- Append retains lower records in order, followed by higher records in assembled
order.
- Whole-sequence replacement retains only the higher sequence, in its assembled
order.
- Replacement by identity replaces matched records in their existing lower
positions and appends new identities in higher-tier order.
For example, lower `[A, B, C]` and higher `[C*, A*, D]` produce
`[A*, B, C*, D]` under replacement by identity. Updating a record's contents
preserves its position; whole-sequence replacement gives the higher tier control
of the complete sequence order.
OD-5 settles default selection and operation naming. OD-6 settles absent
contributions, empty sequences, null, and kind mismatches.
### Mode Names And Effective-Identity Defaults
**OD-5: settled.**
Use the mode names `replace`, `append`, and `upsert`, with one spelling per
operation:
| 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, retain unmatched lower records, and append new identities |
`upsert` preserves the existing positions of matching lower records and adds new
identities in higher-tier order, as settled in OD-4. It does not recursively merge
fields within matching records.
Select the operation at each collection's precedence boundary in this order:
1. Reconcile explicit modes within the current tier under OD-4; conflicting
explicit modes fail.
2. If the current tier supplies an explicit mode, use it in place of a lower mode.
3. Otherwise, retain an applicable explicit mode from a lower tier.
4. If no explicit mode applies, select `upsert` when effective identity is available.
5. Otherwise, select `replace`.
Effective identity may come from an application registration, a current-tier
declaration, or the lower collection, subject to OD-3's agreement rules.
Selection depends on identity availability, not on whether the current tier
explicitly repeats a `key` declaration. With no applicable explicit mode, a plain
overlay sequence can therefore default to `upsert` when identity was established
elsewhere. New sequence values do not reset an existing explicit mode. Inferred
defaults do not become persistent explicit instructions: when no explicit mode
applies, determine the default from the identity available at that boundary.
Determine the inferred mode using the available identity before applying the
operation. `replace`, including a retained explicit mode, follows OD-3: discard
the lower collection's YAML-only identity with the lower sequence, use the
replacing tier's identity declaration if supplied, and preserve application-owned
constraints. Selecting
`append` or `upsert` explicitly still requires an effective identity.
This default governs combination between tiers and during sibling inheritance
under OD-7. Within a tier, identified
sequences append distinct records and reject duplicate identities regardless of
mode. Unidentified sequences retain their existing strict-composition conflict
behavior; this decision does not introduce unkeyed concatenation.
### Whole-Record Replacement By Identity
The `upsert` operation replaces each matching lower record with the
complete higher record, retains unmatched lower records, and adds new identities.
For example, given these lower and higher records:
```yaml
# Lower record
id: B
text: Original
temperature: 0.5
```
```yaml
# Higher record
id: B
text: Revised
```
The resulting record at this precedence boundary contains `id` and `text`.
The lower record's `temperature` is discarded. Subsequent defaulting or other
lifecycle operations retain their separately defined semantics. Recursive
field-by-field overlay of matching records is outside this selected operation.
### Selected Overlay Control
Selected overlay authors must be able to choose any of the three operations
for a sequence supplied by the overlay, including records authored inline.
The operation applies against the existing effective sequence at that path.
For an existing sequence `[foo, bar]`:
| Overlay contribution | Selected operation | Result |
| --- | --- | --- |
| `[foo*]` | Replacement by identity | `[foo*, bar]` |
| `[baz]` | Append | `[foo, bar, baz]` |
| `[bar, baz]` | Whole-sequence replacement | `[bar, baz]` |
Here `foo*` has the same identity as `foo` and supplies its complete replacement
record. Append retains its duplicate-identity error semantics. The retained
records, replacement behavior, and ordering shown are settled, including the
OD-4 rule that matched records retain their lower positions.
The settled OD-2 collection declaration supplies the overlay authoring surface.
OD-5 supplies operation names and effective-identity default selection. The current
[selected-overlay contract](../config.md#selected-overlays) remains the baseline
until the new controls are implemented.
### Optional Application-Owned Identity Constraints
Retain an interface through which applications may register a collection and
impose its unique-key definition. If the application requires `key: id`, a
configuration declaration of `key: [id, name]` is invalid: the compound key
would permit repeated `id` values with different names. The application identity
constraint applies whichever cross-tier operation configuration selects.
This is an authoritative constraint, distinct from an application default that
only fills an omission. Configuration can define identity for collections
without an application-owned constraint. Omitted configuration identity uses a
registered definition when present, and an explicit matching declaration is
accepted, as settled in OD-2. OD-1 below distinguishes the final-result guarantee
for application-owned constraints from assembly-time enforcement for YAML-only
declarations.
### Final-Result Guarantee For Application Registrations
**OD-1: settled, including the YAML-only follow-up below.**
> On successful loading, the final effective collections covered by application
> registrations satisfy their declared identity constraints.
Retain assembly-time identity checks and validate the final effective
collections before publication. This covers the loaded state after defaults,
inheritance, overrides, and application resolution have taken effect. Those
operations retain their separately defined combination semantics. The guarantee
does not require every intermediate callback state to satisfy the constraint
and does not extend to caller mutations after loading returns.
Keep the promise limited to the declared identity constraints. It does not by
itself require registered paths to exist or be non-null. Identity validation checks
actual sequences; the application schema and validation determine whether absence,
null, or another shape is acceptable, as settled in OD-6. Final validation examines
effective typed values, while assembly-time checks retain their role in
validating authored contributions. A final scalar value alone does not prove
that the field was explicitly supplied in a source.
The existing canonical final-value representation and shared publication path
provide the implementation foundation. Diagnostics should identify the affected
collection and item paths, using available origin information without exposing
identity values. The guarantee requires neither a new mutation-history system
nor exact causal reconstruction for records supplied by defaults or callbacks.
### Assembly-Time Enforcement For YAML-Only Identities
Enforce identities declared only in YAML during structural composition,
precedence assembly, and identified collection operations during sibling
inheritance. They do not carry a final-result guarantee after defaulting, CLI
overrides, or application resolution. Those operations may produce a collection
that no longer satisfies a YAML-only identity, including duplicate identities.
OD-7 defines the combination and validation boundaries.
Applications requiring a final identity constraint register it through the
application-owned interface. This keeps YAML declarations aligned with the
structural operations they control and avoids extending their policy lifetime
through later replacements, callbacks, and nested-record changes solely for
final validation.
### Absence, Empty Sequences, Null, And Kind Mismatches
**OD-6: settled.**
At a collection's precedence boundary:
| Higher-tier 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 | Ordinary replacement, subject to application schema | Error: the operation requires a sequence |
A declaration alone does not clear a collection. OD-12 defines how a key-only
declaration validates a retained sequence or remains pending when no sequence
exists. An optional import selecting nothing contributes nothing; it does not
erase records supplied elsewhere.
An absent lower collection can be initialized by an incoming sequence under
any mode, subject to identity requirements. An explicitly null or other
non-sequence lower value is distinct from absence: append and upsert against it
fail, while replacement remains available. An empty incoming sequence does not
bypass these kind checks or the operation's identity requirements.
With effective identity available and no applicable explicit mode, omission
selects upsert. Consequently, `prompts: []` alone does not clear such a collection.
Clearing it requires replace, selected explicitly in the current tier or retained
from a lower declaration. Replacing an identified collection with null likewise
requires replacement and an application schema that permits null.
An application identity registration does not itself require the collection path
to exist or be non-null, and does not independently reject non-sequence values.
Identity validation checks actual sequences; application schema and validation
own the permitted shape. Null as a collection is distinct from a present null
identity component inside a record, which is valid under OD-3.
### Lifecycle Coverage And Sibling Inheritance
**OD-7: settled.**
Apply the sequence operations during source composition and precedence assembly,
selected overlays, and sibling inheritance through `extends`. Preserve the
existing combination semantics of `Definition.Defaults`, CLI overrides, and
application resolution callbacks.
#### Inheritance Operations And Controls
Complete source-tier assembly before resolving inheritance, retaining the
existing dependency-ready inheritance algorithm and scope rules. At each
inheritance combination, the resolved parent's collection is the lower value
and the inheriting mapping's local collection is the higher value. Use the same
operation selection as source precedence: the effective explicit mode wins,
including one retained under OD-9; otherwise use `upsert` with effective identity
and `replace` without it. There is no separate
default-append rule for inheritance.
Use the inheriting destination collection's controls. Its selected mode governs
both source-precedence and inheritance combinations at that destination, without
separate per-phase mode settings. A mode governing the parent's source collection
does not automatically become an instruction at the inheriting destination merely
because records are inherited. Retain the relevant controls until inheritance
uses them, then keep them out of application decoding. Identity agreement,
application constraints, and replacement lifetime follow OD-3.
For example:
```yaml
configkit:
sequences:
- destination: [parent, items]
key: id
child:
items:
- id: foo
value: A
- id: bar
value: B
parent:
extends: child
items:
- id: baz
value: C
```
Despite the mapping names, `parent` inherits from `child` in this example.
The effective identity at `[parent, items]` selects `upsert`, yielding records
`[foo, bar, baz]`. If the local contribution instead supplies a revised `foo`,
the result is `[foo*, bar]`: replace the complete record in its existing position.
Adding `mode: append` would reject that repeated identity; `mode: replace` would
retain only the local sequence.
Mappings continue to combine recursively. Identified sequence records remain
whole-record replacements, not recursive field merges. Identity supplies the
matching key; ConfigKit does not infer identity from a field named `id` or from
record position. Append and upsert continue to require effective identity, and
this extension does not introduce unkeyed sequence concatenation.
Apply OD-4 ordering and OD-6 presence and kind rules at inheritance boundaries.
An omitted local sequence retains the inherited sequence. An explicit empty
sequence clears it under replace, while append and upsert contribute no records.
#### Other Lifecycle Stages
Source-backed underlays participate in the new combination model, including
application-supplied mapping sources. Typed `Definition.Defaults` remain the
initial decoded state: supplied sequences replace default sequences. Source
`mode: append` does not append to a typed default list.
CLI overrides retain explicit replacement for supplied sequences. OD-8 moves
their application after inheritance and typed defaulting, before application
resolution, secret lookup, and final validation.
Application resolution retains its existing assignment and recorded-transformation
semantics; ConfigKit does not automatically combine a callback's assigned sequence
with its previous value. Application-registered identities still receive final
validation under OD-1, independently of how these stages produce their values.
#### Validation Timing
Allow item-local helper imports to complete, and consume applicable non-main
source-version headers, before checking the application payload's identity for
collection assembly. A helper may supply an identity field; a consumed header
cannot supply a component after removal. Validate each identified contribution
before combination and uniqueness in the resulting identified collection,
including inheritance operations. An upsert match across a precedence boundary
is a replacement, not a duplicate error; duplicates within a contribution remain
errors.
Later inheritance, typed defaults, or callbacks do not repair an identity missing
at an earlier assembly boundary. Final application-registered validation examines
the final effective typed collection, while YAML-only enforcement remains limited
to the structural operations described above.
### CLI Precedence And Application Resolution
**OD-8: settled.**
CLI overrides are the highest-precedence input to application resolution. Use
this lifecycle order:
1. Assemble source tiers and selected overlays.
2. Resolve sibling inheritance, then apply typed defaults and decode the assembled
configuration.
3. Apply explicit CLI overrides to that configuration and check supplied values
against the application schema.
4. Run application resolution against the overridden configuration.
5. Resolve effective secret references, validate the final configuration including
application-registered identities, and publish it.
Sequence values supplied by the CLI replace whole sequences, including sequences
inside supplied mappings. Mappings retain ordinary recursive override behavior;
empty sequences and permitted nulls retain their explicit replacement meaning.
Configuration-selected append or upsert does not reinterpret CLI assignments.
Inheritance and defaulting do not run again after CLI application. For inherited
and assembled `[A, B]` overridden by CLI `[C]`, application resolution receives
`[C]`; inheritance cannot reintroduce `[A, B]` afterward.
Application resolution sees CLI values and their explicit-presence and source
metadata. Existing `Resolution.Default` behavior preserves explicit CLI values,
while normalization and recorded application transformations may still change
them. CLI input is authoritative over sources, inheritance, and typed defaults;
it is not immutable during application-owned resolution. For example, a derived
URL can use a CLI-supplied hostname, while an explicitly supplied CLI URL can be
preserved by an absence-based default. Retain existing recording and provenance
requirements for those transformations.
Secret lookup uses the effective references after both overrides and resolution.
Final validation remains after these stages; neither CLI replacement nor
application resolution bypasses identity constraints or semantic validation.
Reject structural directives such as `extends` and `configkit` in CLI override
paths or supplied values. CLI overrides supply application values after structural
processing; they do not initiate a second inheritance pass. This deliberately
changes the current ability to supply or cancel `extends` through CLI input.
CLI changes cannot prevent an earlier inheritance failure or change which
relationships that completed pass resolved. Likewise, because typed decoding
precedes CLI application, an invalid source value that fails decoding cannot be
repaired by a CLI replacement. For example, a string supplied for an integer
field fails before a valid integer override can apply. This differs from the
current override-before-decoding behavior. Semantic validation remains after
overrides and application resolution, as specified above.
This ordering supersedes the current CLI-before-inheritance lifecycle described
in [ADR 0009](../adr/0009-sibling-inheritance.md). It avoids special replacement
markers carried through inheritance. Current contracts remain the implemented
baseline until the architectural and implementation work lands.
### Persistent Operation Policy
**OD-9: settled; this revises the earlier tier-local mode rule in OD-4 and OD-5.**
An explicit mode is collection policy that persists at its applicable destination
until a higher-priority declaration changes it. Reconcile peers strictly within
each tier, then let a higher explicit mode replace a lower one. Omission retains
an applicable explicit mode regardless of whether the higher tier supplies new
sequence values. Source precedence and subsequent inheritance use the same
effective operation policy.
| Lower explicit mode | Higher declaration | Effective mode |
| --- | --- | --- |
| `append` | Mode omitted | `append` |
| `append` | `upsert` | `upsert` |
| `upsert` | `replace` | `replace` |
| None | Mode omitted, effective identity available | Inferred `upsert` |
| None | Mode omitted, no effective identity | Inferred `replace` |
An unrelated overlay leaves the policy intact. A mode-only declaration may change
the policy without supplying records; it neither clears the collection nor
retroactively recombines earlier tiers. A later source contribution or inheritance
operation uses the resulting policy. Supplying values alone does not reset it.
Only explicit modes persist. Inferred defaults are recomputed from effective
identity when needed. Thus explicitly selecting replace for an identified
collection continues to select replace in subsequent tiers until another mode is
declared; an author wanting upsert afterward must select it explicitly. Identity
availability and constraints still follow OD-3, including YAML-only identity
removal on whole-sequence replacement. Persisting a mode does not preserve an
identity that OD-3 discards.
This persistence is scoped to the applicable collection. It does not transfer a
parent mapping's mode automatically to another destination through `extends` or
resurrect policies owned by discarded records. OD-11 addresses nested policy
ownership and transfer, including the distinction between surviving records and
inheritance copies. CLI overrides remain outside sequence mode selection
under OD-8. Strict peer composition and all ordering rules in OD-4 are unchanged.
### Lower-Tier Identity During Strict Composition
**OD-10: settled.**
Allow identity established in accumulated lower tiers to inform strict composition
of a higher tier. Resolve applicable collection identity before combining that
tier's peer contributions, using application registrations, available lower-tier
identity, and declarations gathered within the higher tier's relevant scope.
Authors need not repeat a baseline identity declaration solely because the higher
tier contributes the collection through multiple fragments.
For example, an underlay declares `key: id` for `prompts`; two main-tier fragments
contribute records `[foo]` and `[bar]` without redeclaring a key. The inherited
identity enables those fragments to compose as `[foo, bar]` before the main tier
combines with the accumulated lower collection. If both fragments instead
contribute `foo`, strict composition fails. A selected upsert mode never grants
precedence to peers within the tier.
This does not turn lower YAML identity into an application-owned constraint.
Determine applicability using OD-3's agreement and replacement rules: append and
upsert require agreement, while whole-sequence replacement may change or discard
YAML-only identity. A replacing collection must not be constrained by an obsolete
lower identity merely because policy resolution precedes strict composition.
Application registrations remain authoritative. Under OD-11, a replacement
record also cannot borrow item-local identity owned by the discarded lower
record, even if both records have the same outer identity or numeric position.
Higher-tier declarations do not retroactively repair invalid lower-tier
composition.
Separate source acquisition from policy-aware composition, or otherwise revise
assembly sequencing, so accumulated lower policy is available before higher-tier
strict composition. The current main-before-underlay composition order is not a
constraint on this design. Preserve the distinction between source acquisition
order, strict within-tier dependency-ready scheduling, and precedence order.
The implementation plan must account explicitly for source records, callbacks,
main-version checks, and failure diagnostics when changing the handoffs. Retain
version ownership and validation semantics; do not use higher-tier replacement
to hide lower-tier structural failures.
### Nested Policy Ownership And Transfer
**OD-11: settled.**
Attach item-local collection policies to their owning contributions. Policies
follow surviving records independently of numeric indexes. Whole-record upsert
replacement discards the old record's local policies along with its fields;
whole-sequence replacement likewise discards policies owned by removed records.
A replacement record supplies its own local declarations. Sharing an outer
identity or occupying the same final index does not transfer the discarded
record's policies to its replacement.
For example, a source imported as one record into `prompts` can declare:
```yaml
configkit:
sequences:
- destination: [steps]
key: id
mode: append
id: foo
steps:
- id: prepare
```
The declaration governs `steps` inside that particular `foo` record. Retaining
`foo` retains its local policy, including its explicit mode under OD-9. Upserting
a new `foo` discards the old `steps` and its declaration; the replacement does not
inherit either merely because upsert retains the outer record's position.
Distinguish inherited collection identity from operation instructions. YAML
identity follows a collection copied through sibling inheritance, subject to
OD-3's agreement and replacement rules. The inheriting destination selects its
operation under OD-7 and OD-9; the source collection's mode does not automatically
become an instruction at the new destination.
For example, a source mapping's `steps` collection has `key: id` and
`mode: append`. An inheriting mapping supplies local `steps` but no applicable
explicit mode. The copied identity selects upsert at the inheriting destination;
the source's append mode remains applicable at its own destination. An explicit
append declaration at the inheriting destination instead selects append there.
A destination declaration of the same key agrees; a different key conflicts when
combining records, while explicit replacement can establish a new YAML identity
under OD-3.
Application registrations remain constraints at their registered destinations;
copying a collection does not create another application registration. Policies
that independently apply at the destination are not discarded merely because
an individual record is replaced. Ownership determines which declarations are
removed. This preserves OD-9's persistence rule for applicable explicit modes
without retaining declarations owned by discarded records. The implementation
plan should choose the bookkeeping needed to preserve these distinctions.
### Identity Declarations Without Sequence Values
**OD-12: settled.**
Allow identity declarations without accompanying records. Apply a declaration to
an existing retained sequence immediately, checking identity agreement and
validating its records. Do not recombine earlier tiers. For example, if a lower
tier supplies an unkeyed `prompts` sequence and a higher tier only declares
`destination: [prompts], key: id`, the retained records must satisfy that identity
at this boundary.
If the collection is absent, retain the declaration in its owning scope for later
contributions without requiring the collection to exist. An otherwise empty
underlay can therefore establish identity for main-tier fragments under OD-10.
Item-local declarations remain subject to OD-11's ownership rules.
Merely declaring replace without supplying a replacement value does not authorize
changing a retained collection's identity. Actual replacement of the collection
by null or another non-sequence value discards the lower YAML-only identity,
retains any identity explicitly declared by the replacing tier, and preserves
application constraints. This extends OD-3's replacement rule to non-sequence
values.
An explicit identity declaration can remain pending at an absent or non-sequence
destination without independently imposing a shape requirement. OD-6 still
governs whether an attempted operation on a non-sequence value fails. These rules
allow baseline policy declarations while keeping identity removal tied to actual
replacement. Explicit mode persistence remains governed by OD-9.
## Architectural Direction
The settled collection declaration gives imported and inline records at the same
destination a shared identity policy. Declarations are gathered before composing
the relevant scope.
OD-3 settles identity agreement and lifetime; OD-4 settles tier-wide operation
selection, strict peer composition, and ordering at precedence boundaries.
OD-10 requires resolving applicable lower-tier policy before higher-tier strict
composition, independently of source acquisition order.
This requires collection policy to survive import expansion through precedence
assembly and the applicable inheritance operations. Correctness includes final
values, projected item paths, source destinations, provenance, history,
sensitivity, and passive resource contexts.
Appending changes final indexes; replacing changes which contributions survive.
The implementation must account for both through the existing loading lifecycle.
## Scope And Decision Ownership
This is narrower than the deferred
[identified-record sequences proposal](future.md#1-identified-record-sequences),
which also considers recursive merging of matching records and record-specific
inheritance. Those capabilities remain separate candidates. Compound keys,
duplicate-rejecting append, and whole-record replacement by identity do not
require adopting them.
The proposal would extend identity declaration ownership and revise the
cross-tier replacement decision in
[ADR 0014](../adr/0014-sequence-placement-and-composition.md), as well as sequence
replacement during inheritance in
[ADR 0009](../adr/0009-sibling-inheritance.md), including its CLI-before-inheritance
lifecycle ordering. Record
the architectural change in a superseding decision and reconcile affected
architecture policy before implementation. The current ADR and contracts remain
authoritative until that decision is made. This draft neither changes them nor
selects migration machinery.
The settled roadmap is ready for a staged implementation plan.
The plan should cover ordinary and layered loading, item-local scopes, source-version
processing, identity validation, precedence, inheritance, and inspection together.
Public API design and test sequencing belong to that later plan.