Draft sequence identity and layered append proposal
This commit is contained in:
270
docs/roadmap/sequences.md
Normal file
270
docs/roadmap/sequences.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# Configuration-Declared Sequence Identity And Layered Append
|
||||
|
||||
Status: draft proposal for discussion. This document captures the requested
|
||||
capabilities and initial recommendations; the open decisions are not settled.
|
||||
It is not an implementation plan or an implemented contract. All new syntax
|
||||
shown below is illustrative.
|
||||
|
||||
## 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 explicitly.
|
||||
|
||||
The motivating example is a prompt collection whose defaults supply some
|
||||
records and whose application or installation layers add others. A repeated
|
||||
identity should produce an error when records are combined. This proposal
|
||||
concerns adding distinct records and replacing whole collections; matching
|
||||
records would not acquire field-by-field update semantics merely by adding
|
||||
this feature.
|
||||
|
||||
## 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.
|
||||
|
||||
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 import syntax introduces an identity declaration, tentatively
|
||||
named `key`, that accepts either one field or a list of fields for a compound
|
||||
identity. The original proposal is to move the application's sequence
|
||||
registration into configuration. Whether to remove, retain, or adapt the Go
|
||||
registration remains open.
|
||||
|
||||
```yaml
|
||||
configkit:
|
||||
imports:
|
||||
- dir: prompts
|
||||
pattern: '*.yaml'
|
||||
destination: [prompts]
|
||||
placement: sequence-item
|
||||
key: [namespace, id]
|
||||
append: true
|
||||
```
|
||||
|
||||
Each selected file would contribute a record such as:
|
||||
|
||||
```yaml
|
||||
namespace: support
|
||||
id: welcome
|
||||
text: Welcome
|
||||
```
|
||||
|
||||
Here `append: true` illustrates retaining a lower-layer collection and adding
|
||||
distinct records from the higher layer. The user also proposed `append: false`
|
||||
or an inverse `replace: true` choice for complete replacement. An explicit
|
||||
operation such as `mode: append` or `mode: replace` is another candidate;
|
||||
field spelling and default behavior remain open.
|
||||
|
||||
The intended results can be expressed without settling the syntax. Letters
|
||||
below denote record identities:
|
||||
|
||||
| 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]` |
|
||||
|
||||
Replacement discards the lower sequence as a whole. 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.
|
||||
|
||||
## Initial Architectural Direction
|
||||
|
||||
Treat identity as a property of the destination collection, even if an import
|
||||
introduces its declaration. That property needs to cover both imported items
|
||||
and records authored directly at the same destination. Discovering collection
|
||||
declarations before composing the relevant scope would make their application
|
||||
independent of which contribution happens to be assembled first.
|
||||
|
||||
The initial recommendation is to compose each tier's contributions strictly,
|
||||
then apply one collection operation from the higher tier against the lower
|
||||
sequence. An individual import would therefore express policy for its tier's
|
||||
contribution at that destination. It would not sequentially erase contributions
|
||||
from peer imports. Multiple agreeing declarations could coalesce; conflicting
|
||||
explicit identity or operation declarations would produce errors.
|
||||
|
||||
For append, retain lower-tier item order and then the higher tier's assembled
|
||||
item order. Existing dependency-ready scheduling would continue to determine
|
||||
assembly order within a tier. Replacement would retain only the higher tier's
|
||||
assembled sequence. These are 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](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 and
|
||||
duplicate-rejecting append do not require adopting them.
|
||||
|
||||
The proposal would revise the application-owned registration and cross-tier
|
||||
replacement decisions in
|
||||
[ADR 0014](../adr/0014-sequence-placement-and-composition.md). 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. Who Owns Identity Policy?
|
||||
|
||||
Should configuration replace `Definition.SequenceCompositions`, supplement it,
|
||||
or merely provide defaults for an application-owned policy?
|
||||
|
||||
**Initial recommendation:** Let configuration own identity for collections
|
||||
assembled through the new mechanism, with applications retaining responsibility
|
||||
for their domain requirements. Resolve the treatment of existing Go registration
|
||||
before removing it: applications may depend on a fixed identity definition even
|
||||
when configuration authors control source layout. Avoid two independent policy
|
||||
systems whose precedence depends on acquisition order.
|
||||
|
||||
**Alternative:** Keep application registration authoritative and allow YAML to
|
||||
declare or confirm compatible policies. This preserves schema-owned identity
|
||||
constraints and supports programmatic use, but adds a reconciliation rule and
|
||||
retains some application setup. YAML-only ownership is simpler for independently
|
||||
authored collections but moves enforcement of fixed domain identities elsewhere.
|
||||
|
||||
### OD-2. Where Does Configuration Declare Collection Policy?
|
||||
|
||||
Is `key` on a `sequence-item` import sufficient, or should configuration have a
|
||||
collection declaration that also covers inline sequences, ordinary mapping
|
||||
imports containing sequences, and selected overlays? How does such a declaration
|
||||
address a collection inside an imported item?
|
||||
|
||||
**Initial recommendation:** Define one destination-scoped collection policy
|
||||
model first. Evaluate a configuration-level declaration alongside import syntax
|
||||
against these workflows before selecting the surface. Collection identity must
|
||||
apply consistently to all participating records at that destination, including
|
||||
inline records. Resolve relative destinations and item-local scopes using their
|
||||
existing ownership boundaries.
|
||||
|
||||
**Alternative:** Limit this feature to policies declared on item imports. This
|
||||
keeps the initial syntax small and directly serves imported prompt collections,
|
||||
but leaves inline-only collections and overlay-authored append without a natural
|
||||
declaration site. A separate collection declaration covers those cases at the
|
||||
cost of more configuration structure; an import shorthand would require a clear
|
||||
relationship to that 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 replacement can introduce a different identity
|
||||
definition after discarding the lower collection.
|
||||
|
||||
**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 an append must
|
||||
agree; equal definitions within a scope coalesce and conflicting definitions
|
||||
fail. Detailed list validation and identity changes at 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 An Import's Append Or Replace Choice Control?
|
||||
|
||||
What happens when peer imports into the same destination request different
|
||||
operations, or when some contributions omit the operation?
|
||||
|
||||
**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 inherit the effective collection operation once its
|
||||
declaration site is selected.
|
||||
|
||||
**Alternative:** Treat each import as a sequential append-or-replace operation.
|
||||
This permits deliberate resets among imports, but a replacement can discard peer
|
||||
contributions and would require an explicitly selected operation-order model.
|
||||
Another alternative is to allow the choice only on a collection declaration,
|
||||
which reduces conflicts between import annotations but changes the proposed
|
||||
authoring surface.
|
||||
|
||||
### OD-5. What Is The Default Operation And Its Syntax?
|
||||
|
||||
Choose between `append`, an inverse `replace` option, or a named operation, and
|
||||
decide what omission means.
|
||||
|
||||
**Initial recommendation:** Keep replacement as the cross-tier default and
|
||||
make append explicit. Lists can represent complete ordered instructions as well
|
||||
as extensible catalogs; an explicit append communicates the intent to retain
|
||||
lower-layer records. Prefer a positive named operation if both modes need equal
|
||||
prominence, while considering the simplicity of `append: true`.
|
||||
|
||||
**Alternative:** Default identified collections to append and require explicit
|
||||
replacement. This makes progressive collections concise, but retains lower-layer
|
||||
records unless an author deliberately clears them. An inverse `replace` boolean
|
||||
expresses that model compactly; a named operation makes both choices explicit.
|
||||
|
||||
### 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 append or replace?
|
||||
|
||||
**Initial recommendation:** An optional import selecting nothing contributes
|
||||
nothing and does not clear lower values. An explicitly supplied empty sequence
|
||||
with replacement can clear a collection; append of an empty sequence adds 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?
|
||||
|
||||
Does append 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 retained append result. Preserve existing
|
||||
typed-default and application-resolution semantics unless their inclusion is
|
||||
explicitly chosen. Decide selected-overlay coverage with OD-2; it is part of
|
||||
source precedence but currently has no import declaration site. Account for
|
||||
item-local helpers and source-version consumption before deciding identity is
|
||||
available, consistent with the current lifecycle.
|
||||
|
||||
**Alternative:** Apply collection policy throughout defaulting, inheritance,
|
||||
overrides, and application resolution, including final identity checks. This
|
||||
would provide a broader final-collection guarantee, but requires additional
|
||||
combination, callback, and inspection rules. Supporting typed default lists is
|
||||
a separate scope choice from extending source-backed baseline collections.
|
||||
Reference in New Issue
Block a user