22 KiB
Sequence Identity And Cross-Tier Combination
Status: draft proposal for discussion. This document captures the requested capabilities, settled design choices, and initial recommendations. Choices under Open Decisions remain unsettled. It is not an implementation plan or an implemented contract. The declaration structure is settled under OD-2; operation names in examples remain illustrative pending OD-5.
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.
Current Baseline
The import contract 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 owns these
rules; layered loading owns tier precedence.
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.
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.
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:
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;
operation names and default behavior remain under OD-5. Identity and combination
controls belong to the collection declaration; import shorthand is deferred.
The intended results can be expressed without settling operation names. Letters
below denote record identities; B* is a higher-tier record with the same
identity as B and a different payload. The outcomes are settled; the ordering
shown is the recommendation discussed below.
| Lower-tier sequence | Higher-tier sequence | Operation | Proposed result |
|---|---|---|---|
[A, B] |
[C] |
Append | [A, B, C] |
[A, B] |
[C] |
Whole-sequence replacement | [C] |
[A, B] |
[B*] |
Append | Duplicate-identity error |
[A, B] |
[B*] |
Whole-sequence replacement | [B*] |
[A, B] |
[B*] |
Replacement by identity | [A, B*] |
[A, B] |
[B*, C] |
Replacement by identity | [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.
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 remains under OD-3.
Mode describes the current tier's combination instruction. A lower tier's
append does not silently select append for a later overlay. Gather collection
declarations before composing their relevant scope so their effect does not
depend on which contribution is assembled first. Agreement and omission among
peer declarations remain under OD-4.
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:
configkit:
sequences:
- destination: [prompts]
key: id
mode: replace-by-key
prompts:
- id: foo
text: Revised definition
An overlay can instead add a distinct record:
configkit:
sequences:
- destination: [prompts]
key: id
mode: append
prompts:
- id: baz
text: Another prompt
Or it can explicitly select whole-sequence replacement:
configkit:
sequences:
- destination: [prompts]
mode: replace
prompts:
- id: bar
text: Bar definition
- id: baz
text: Baz definition
Explicitly selecting the default operation is valid. Under the recommended
whole-sequence replacement default, removing the last example's configkit
block produces the same result. An author may keep it to make intent explicit.
The names replace, append, and replace-by-key illustrate the three
operations; exact names and the default remain under OD-5.
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.
Whole-Record Replacement By Identity
The third cross-tier 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:
# Lower record
id: B
text: Original
temperature: 0.5
# 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 and replacement behavior are settled; ordering shown follows the
recommendation under OD-4.
The settled OD-2 collection declaration supplies the overlay authoring surface. OD-5 retains operation naming and default selection. The current selected-overlay contract 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. The final-result guarantee for application-owned constraints is settled below; YAML-only enforcement remains an open follow-up to OD-1.
Final-Result Guarantee For Application Registrations
OD-1: settled for application-owned constraints.
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 settle null and non-sequence handling; those boundaries remain under OD-3 and OD-6. Final validation examines effective typed values, while assembly-time checks retain their role in validating authored contributions. A final string 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. Final enforcement for identities declared only in YAML is not settled here.
Initial 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.
The initial recommendation is to compose each tier's contributions strictly, then apply one collection operation from the higher tier against the lower sequence. A collection declaration would therefore govern its tier's contribution at that destination, without sequentially erasing peer contributions. Multiple agreeing declarations could coalesce; conflicting explicit identity or operation declarations would produce errors.
For append, the ordering recommendation is to retain lower-tier item order and then the higher tier's assembled item order. For replacement by identity, retain the position of each replaced record and append new identities in higher-tier order. Existing dependency-ready scheduling would continue to determine assembly order within a tier. Whole-sequence replacement would retain only the higher tier's assembled sequence. Ordering and the treatment of peer declarations remain recommendations awaiting agreement, not changes to the current ordering contract.
This requires collection policy to survive import expansion through precedence assembly. 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, 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. If adopted, 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.
A staged implementation plan should follow resolution of the questions below. It should cover ordinary and layered loading, item-local scopes, source-version processing, identity validation, precedence, and inspection together. Public API design and test sequencing belong to that later plan.
Open Decisions
OD-1 Follow-Up. Final Validation For YAML-Only Identity Declarations
Application-registered collections have the settled final-result guarantee above. Should a configuration-only identity declaration carry the same guarantee, or govern structural assembly alone?
Initial recommendation: With declaration scope settled in OD-2, resolve identity-policy lifetime across replacement in OD-3 before this choice. Final enforcement needs an unambiguous effective policy for each retained collection. The application-owned guarantee can proceed independently of this choice.
Alternatives: Applying the same final check to YAML-declared identities would give both declaration forms consistent enforcement, but requires resolving which policy survives replacement or follows nested records. Limiting YAML-only identity to assembly keeps policy lifetime narrower, but later operations could produce a final collection that no longer satisfies that identity declaration.
OD-3. What Constitutes Identity, And When Can It Change?
Settle compound-key field selection, value types, missing/null components, empty and repeated field lists, empty identity strings, and agreement between declarations. Decide whether whole-sequence replacement can introduce a different identity definition after discarding the lower collection when the application has imposed no identity constraint. An application-owned constraint remains authoritative.
Initial recommendation: Interpret key: id as [id] and a compound key as
an ordered tuple of direct, literal string fields. Retain exact string comparison
and require every component to be present. Preserve tuple boundaries rather than
concatenating values. Identity declarations participating in append or replacement
by identity must agree; equal definitions within a scope coalesce and conflicting
definitions fail. Detailed list validation and identity changes at whole-sequence
replacement boundaries remain to be settled.
Alternative: Permit additional scalar types or nested field paths. These support more schemas but add type-equality and addressing rules. A load-wide fixed identity definition simplifies agreement across tiers; allowing a replacement to establish a new definition gives independently authored layers more flexibility.
OD-4. What Does A Combination Choice Control?
What happens when peer collection declarations at the same destination request different operations, or when some contributions omit the operation? Settle ordering for replacement by identity, including where replaced records and new identities appear. Apply the same operation semantics to selected overlay contributions.
Initial recommendation: Assemble one strict sequence per destination in each tier, then apply its agreed operation at the precedence boundary. Reject conflicting explicit choices within a tier. This preserves peer composition and prevents declaration or scheduling order from granting replacement authority. Define how omitted choices use the effective operation within the current tier; OD-2 settles that a lower tier's mode is not inherited by a later tier. Duplicate identities among peers would remain strict-composition errors under all three cross-tier operations. For replacement by identity, keep matched records in their lower-tier positions and append new identities in higher-tier order.
Alternative: Apply collection declarations as sequential operations on their associated source contributions. This permits deliberate resets within a tier, but a replacement can discard peer contributions and would require an explicitly selected operation-order model.
For ordering, moving replaced records to their higher-tier contribution positions is another viable choice. It gives the higher layer more influence over ordering, but changing a record's payload could also move it relative to retained records.
OD-5. What Is The Default Operation And Its Syntax?
Choose names for the three mode values and decide the default operation.
OD-2 settles the declaration structure and accepts explicit selection of the
default. Omission among peer declarations remains under OD-4.
Initial recommendation: Keep whole-sequence replacement as the cross-tier
default and make append and replacement by identity explicit. Lists can represent
complete ordered instructions as well as extensible catalogs; an explicit append
communicates the intent to retain
lower-layer records. Use names that distinguish whole-sequence from whole-record
replacement; the examples use replace, append, and replace-by-key.
Alternative: Default identified collections to append or replacement by
identity and require explicit whole-sequence replacement. These defaults make
progressive collections concise, but retain lower-layer records unless an author
deliberately replaces the collection. For naming, upsert is a concise alternative
to replace-by-key, but requires explaining its whole-record replacement behavior
to authors unfamiliar with that term.
OD-6. What Do Absence, Empty Sequences, And Null Mean?
Can a declaration selecting nothing affect a lower collection? How do explicit empty sequences, null, and non-sequence values interact with all three operations?
Initial recommendation: An optional import selecting nothing contributes nothing and does not clear lower values. An explicitly supplied empty sequence with whole-sequence replacement can clear a collection; append or replacement by identity with an empty sequence contributes no records. Preserve the distinction between absence and supplied values. Decide null and kind-mismatch handling explicitly rather than silently treating them as empty sequences.
Alternative: Treat an explicitly declared replacement operation as a reset even when its import selects nothing. This provides a compact clearing mechanism, but makes an optional missing file or empty search destructive. Keeping clearing as an explicit value avoids that coupling.
OD-7. Which Lifecycle Stages Participate?
Do the new cross-tier operations extend only source-backed tiers, or also
Definition.Defaults, inheritance, CLI overrides, and application-created records?
At which boundaries
must identity be validated, including helper-supplied fields and consumed source
version headers?
Initial recommendation: Start with source-backed strict composition and precedence assembly. Validate records at the relevant collection assembly boundaries and uniqueness across each combined result. Preserve existing typed-default and application-resolution semantics unless their inclusion is explicitly chosen. Selected-overlay combination is included in the settled scope; its declaration mechanism is settled in OD-2. Account for item-local helpers and source-version consumption before deciding identity is available, consistent with the current lifecycle.
The settled OD-1 guarantee covers final validation of application constraints. Validating the final collection does not itself change how defaults, inheritance, CLI overrides, or callbacks combine values.
Alternative: Extend the combination operations through defaulting, inheritance, overrides, and application resolution. This would support progressively extending collections across more of the lifecycle, but requires additional combination, callback, and inspection rules. Supporting typed default lists is a separate scope choice from extending source-backed baseline collections; the application-owned final-result guarantee remains independent of this combination-scope decision.