Skip to content

loader: promote x-* extensions adopted by the specification - #935

Merged
glours merged 5 commits into
compose-spec:mainfrom
ndeloof:x-aliases-promotion
Sep 29, 2026
Merged

glours merged 5 commits into
compose-spec:mainfrom
ndeloof:x-aliases-promotion

Conversation

@ndeloof

@ndeloof ndeloof commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does, in one sentence

Promotes an x-* extension to the attribute the specification adopted it as, when that attribute is not already set.

Context

Compose lets experiments use x-* attributes without any check. When one of them is adopted (x-develop became develop, x-initialSync became initial_sync), files written with the extension silently stop working, unless every consumer reads both spellings. The adoption of initial_sync is an example: the loader now only knows the new name.

What the PR brings

  • A table of adopted extensions, starting with x-develop and x-initialSync. Adopting the next attribute is one line, and a test checks that each entry targets an attribute of the schema.
  • Files keep working under either spelling, and consumers only ever see the attribute. When both are set on the same service, the extension is merged under the attribute with the usual merge rules: the attribute wins on conflicting values, and an incompatible shape is reported.
  • Each file is promoted on its own, before include, extends and merge: an override written with the extension still overrides its base file, and included or extended services are promoted too.
  • !reset and !override written on the extension act on what earlier files declared under the attribute's name.
  • A value that is invalid for the attribute is now rejected by the usual validation, with the attribute's path, instead of being ignored.
  • Files that use none of these extensions are unaffected.

Why this shape

The table lives in the loader rather than in the schema: it only concerns how this library reads files written before an adoption, and it needs no change in the specification. Should an adoption change the shape of the value, the entry is where a conversion would go, as a follow-up.

πŸ€– Generated with Claude Code

@ndeloof
ndeloof requested a review from glours as a code owner September 29, 2026 07:36
An attribute adopted by the specification, such as develop, was previously used
as an x-* extension. Files written with the extension silently stop working
unless each consumer reads both spellings.

Declare such extensions in a table and, while loading each compose file, right
after interpolation and before include, extends and merge, promote them to the
attribute they stand for when it is not already set. Promoting file by file
lets an override written with the extension still override its base file.

The table starts with x-develop and x-initialSync.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
…ds for

The paths recorded for !reset and !override name the extension as written in
the file, while earlier files hold the attribute under its own name once their
extensions are promoted, so the tag had no effect on them. Record the resolved
path next to the original one.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof force-pushed the x-aliases-promotion branch from 5c8d84d to d29c74a Compare September 29, 2026 08:00
@ndeloof ndeloof changed the title loader: promote x-* aliases declared in the schema loader: promote x-* extensions adopted by the specification Sep 29, 2026

@glours glours left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loader/tests/extensions_test.go β€” TestExtensionAliasWithReuse/extends

Nothing pins the position of promoteAliases (loader/loader.go:513). Moving the call to just before override.Merge(dict, cfg) keeps ./loader/... fully green, but changes the result:

  • base has develop.watch=[a], web extends it and sets x-develop.watch=[b]: expected [a, b], got [a]. The user's x-develop is silently ignored and left as an extension.
  • base has x-develop.watch=[a], web extends it and has its own develop.watch=[b]: expected [a, b], got [b], with a leftover x-develop.

The current extends subtest doesn't catch it because web has no develop of its own, so promotion still lands before the merge. Moving the call after the merge or removing it is caught (8 subtests fail), so the gap is only "before merge but after extends".

Suggested case, next to the existing extends subtest:

name: test
services:
  base:
    image: app
    develop:
      watch:
        - path: src
          action: sync
          target: /app
  web:
    extends:
      service: base
    x-develop:
      watch:
        - path: other
          action: rebuild

expected web.develop.watch = [src, other].

Cover an extension set on the extending service or on the base service,
so that promoting after the extends merge is caught.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
When a service sets both the extension and the attribute, promote the
extension and merge it under the attribute with the usual merge rules
instead of leaving it aside. The attribute still wins on conflicting
values, and incompatible shapes are reported.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Comment thread loader/aliases.go Resolved
Comment thread loader/aliases.go Resolved
deepClone (extends.go) already does the same deep copy; drop the
duplicate helper and call it from promoteAliases.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof

ndeloof commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

@glours re your review: the ordering concern (nothing pinned promoteAliases's position relative to extends, so a future refactor could silently drop the alias) is addressed in d2ffe87 β€” it adds the two suggested regression cases ("extends with the extension on the extending service" and "extends with the extension on the base service"), which pass with the current implementation.

@glours glours left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@glours
glours merged commit 98fd653 into compose-spec:main Sep 29, 2026
8 checks passed
@ndeloof
ndeloof deleted the x-aliases-promotion branch September 29, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants