Files
ConfigKit/docs/adr/0017-collection-sequence-policy.md

144 lines
7.6 KiB
Markdown

# ADR 0017: Define Identity and Merge Rules for Each Collection
## Status
Accepted.
This supersedes the decisions about who declares identity and how
sequences combine in [ADR 0014](0014-sequence-placement-and-composition.md),
and the rule that inheritance always replaces sequences in
[ADR 0009](0009-sibling-inheritance.md). It also extends that ADR's sibling
scopes to identified sequences. Existing mapping-sibling scopes and source
placement continue to apply. It supersedes the whole-sequence
precedence replacement rule in the proposed
[ADR 0007](0007-source-backed-underlays.md).
## Date
2026-10-22
## Context
ConfigKit can load separate files as records in a sequence. Applications can
register an identity key so records with different identities combine and
duplicates fail. However, a higher configuration layer or an inheriting mapping
currently replaces the whole sequence. Applications that need to extend a
catalog across layers must write their own merging code and keep source and
history information consistent with its results.
Configuration authors need to choose how records are identified and whether a
layer adds records, updates records, or replaces the collection. Applications
also need a way to require unique identities in the final loaded configuration.
These rules must work consistently for imported records, inline sequences,
overlays, and inheritance.
Records in a sequence may also share settings. Authors need to reuse those
settings through inheritance while keeping each record's own identity.
## Decision
ConfigKit manages identity and merge rules for each collection during loading.
Configuration files can declare those rules for imported and inline sequences,
including sequences in selected overlays. Applications can require a particular
identity definition, and configuration declarations must agree with it. Source
placement determines where each contribution enters the configuration.
Keep the distinction between contributions at the same priority and overrides
from a higher layer, as established in
[ADR 0001](0001-separate-composition-and-overlay.md). Within one tier, records
with distinct identities combine; duplicate identities and conflicting
declarations fail. Determine the applicable rules before combining those
contributions, including any identity established in lower tiers.
Across layers, support three operations: replace the whole sequence, append
records while rejecting duplicate identities, or replace whole records by
identity (`upsert`). Source layering and sibling inheritance use the same
operations. For inheritance, the rules at the inheriting destination determine
which operation applies.
An identified sequence is also a sibling scope for inheritance. A record may
inherit from one other record in the same sequence, selected by its complete
identity. Each record supplies its own identity before inheritance. The child
keeps that identity and combines its settings with the parent's other fields,
using the existing mapping and collection rules. Parents remain ordinary records
subject to normal validation.
Resolve these relationships after source assembly, using the same dependency
ordering as mapping-sibling inheritance. Resolve nested relationships at each
inheriting destination. Preserve the assembled sequence order. Whole-record
upsert keeps its existing meaning; inheritance then uses the records that remain.
Typed defaults, CLI overrides, and application resolution follow the lifecycle
in [ADR 0018](0018-cli-after-structural-resolution.md).
An explicitly selected operation remains in effect at its destination until a
higher layer changes it. When no explicit operation applies, use upsert if an
identity definition is available; otherwise replace the whole sequence. Authors
can declare identity and an operation before supplying any records. Keep source
acquisition order separate from layer priority and the dependency ordering
established by [ADR 0015](0015-shared-dependency-ready-ordering.md).
Rules declared inside a record belong to that record. Retaining the record
retains its local rules; replacing it discards them. Replacing a whole collection
can discard its previous YAML identity definition, while application requirements
still apply. Inheritance can copy collection identity, but the inheriting
destination chooses its operation. Application registrations continue to govern
the paths where they were registered. The same ownership rules apply to
declarations made before a sequence exists.
Check YAML-declared identity when combining collections during source assembly
and inheritance. Application registrations also require the final loaded
sequences to satisfy their identity rules, including after typed defaults, CLI
overrides, and application resolution. This guarantee checks sequences that
exist; the application schema and validation decide whether a collection must
exist and which value types are allowed. The guarantee applies when loading
completes, rather than to every intermediate callback state or changes made by
the caller afterward.
Preserve explicit presence, source attribution, history, sensitivity, and
resource context through these operations, following
[ADR 0003](0003-preserve-presence-and-resolution-history.md). Typed defaults,
CLI assignments, and application resolution keep their own rules for changing
values. The public API provides registration and inspection; the implementation
chooses how to store and track collection rules internally.
## Alternatives Considered
- **Keep identity application-owned and always replace sequences across layers.**
Keeps the current design small, but reusable catalogs require application
changes or custom merging code.
- **Put merge rules on each import.** Keeps the rules beside the imported source,
but inline sequences and overlays need a separate way to declare them. It also
makes rules for a collection spread across several imports harder to reconcile.
- **Merge individual fields of records with matching identities.** Allows partial
record updates, but adds rules for field conflicts and retained values.
Replacing the whole record makes the update's scope clear.
- **Keep inheritance limited to mapping siblings.** Keeps parent lookup simpler,
but authors of sequence records must duplicate shared settings or applications
must implement inheritance themselves. Collection identity provides a way to
select a parent within the existing loading process.
- **Check every YAML identity again at the end of loading.** Gives YAML declarations
authority over values changed by defaults, CLI assignments, and application
resolution. This extends the lifetime of those declarations; application
registration provides that final guarantee when needed.
## Consequences
Consumers can build and customize catalogs in one load while retaining useful
inspection information. Configuration authors choose how collections combine
within application requirements. Duplicate identities among contributions at
the same priority remain errors.
Existing registered collections use upsert across layers by default unless an
explicit operation selects otherwise. Correct loading now requires tracking
which declarations still apply as records are replaced, copied, or moved to new
positions. Lower-tier identity must be available before combining higher-tier
contributions.
Record inheritance also requires parent lookup by identity and checking for
missing parents and cycles. Copied settings retain the same source, history,
and sensitivity information as mapping-sibling inheritance.
The configuration contract will define declaration syntax, identity comparison,
omission and empty-value behavior, and diagnostics when the feature is
implemented.