Repository navigation
feat(loader): overlays patch builds, exposes and consumes by identity (schema 0.7.6) - #732
Merged
Merged
Conversation
… from 0.7.6 _deep_merge patched lists of maps by position, so inserting or reordering an entry in the base silently retargeted every overlay. With builds [ingest, transform] and overlays/prod.yaml raising builds[1], adding a backfill build first made prod patch ingest; the patch also copied id: transform onto it, and `fluid plan --env prod` failed with planner_failed 'schedule_transform' and no word of why. In a contract whose fluidVersion is 0.7.6 or later (the overlay's own fluidVersion counts), builds[].id, exposes[].exposeId and consumes[] by productId + exposeId match an overlay entry to its base entry when the base's entries carry unique ones, the way Kubernetes server-side apply merges a list by its list-map keys and jsonmerge's arrayMergeById (MIT) merges by id; the semantics are borrowed, not the dependency. An overlay entry that matches nothing is appended and logged as overlay_entry_added, naming the base's ids so a typo shows. An overlay entry the merge cannot match, no id or an id named twice, raises OverlayMergeError with its index: falling back to position would retarget it, the failure this fixes. load_with_overlay and `fluid bundle --env` report it with the overlay's path, and the contract API as contract_load_failed. Id-less patches still apply to a base with at most one entry. A base whose own ids are missing or repeated keeps the positional merge and logs overlay_merged_by_position. Older contracts merge as before and log overlay_patch_retargeted when a patch lands on an entry that names another id, or another part of a consumes key. Each warning names its contract and is logged once per process, since apply loads its contract twice; note_missing_overlay dedupes the same way. Only the three top-level lists are keyed; lists inside an entry, and a nested key named builds, still merge by position. The rule sits in _deep_merge, so every command that applies an overlay, `fluid bundle --env` and the contract API merge the same way.
…ity (schema 0.7.6) An Upgrade note for 0.7.6 contracts whose overlay names ids at other positions, adds entries, or leaves an id out, and Added entries for the keyed merge and the retarget warning older contracts now get.
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
5 of 9 tasks
…ader The overlay tests imported fluid_build.loader both as a module and by name (code-quality review on #732).
…-merge # Conflicts: # CHANGELOG.md
This was referenced Oct 19, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Overlays deep-merge lists of maps by position (
loader._deep_merge). In a contract with more than one build or expose, inserting or reordering an entry in the base silently retargets every overlay. Take the requester's case: the base hasbuilds: [ingest, transform], andoverlays/prod.yamlraisesbuilds[1], meaningtransform. Add abackfillbuild first, and prod now patchesingest. It also copiesid: transformonto it, andfluid plan --env prodfails withplanner_failed: 'schedule_transform', with no word of why (reproduced onmain).This PR does three things.
From
fluidVersion0.7.6 (the schema preview), overlays merge by identity.builds[].id,exposes[].exposeId, andconsumes[]byproductId+exposeId. They match an overlay entry to its base entry when the base's entries carry unique ones.overlay_entry_added. The warning names the base's ids, so a typo shows.fluid validate --envandload_with_overlayreport it with the overlay's path,fluid bundle --envdoes too, and the contract API raisescontract_load_failed. Id-less patches still apply to a base with a single entry.overlay_merged_by_position.Older contracts merge exactly as before, and now warn.
overlay_patch_retargetedfires when a positional patch lands on an entry that names another id, or another part of aconsumeskey. It names both ids, the fix, and the opt-in.One chokepoint. The rule sits in
_deep_merge, so every overlay path uses it:fluid bundle --envandgenerate artifacts;Each warning names its contract and is logged once per process (apply loads its contract twice), as
note_missing_overlayalready does.The requester's part 3, the safety net without keyed merge, is the 0.7.5 warning. It warns and does not fail under
--strict;--strictdoes not see loader warnings today.Borrowed, not built
x-kubernetes-list-map-keys, compound keys such ascontainerPort+protocol). An entry matches on every key field, and an unmatched entry is added. Like SSA, entries the merge cannot key are refused, not merged by position.arrayMergeById(MIT): an id read from the entry, with compound ids.jsonschemaonto the light CLI's cold path, so we borrow the semantics, not the dependency.Validation
tests/test_overlay_keyed_merge.py(28 tests, several parametrized). The 9 original behaviour tests fail onmain.len(base) > 1guard;main. Perf startup budget: 38 passed. Lint and format: whole-tree ruff, black 24.10.0, the dead-code gate, license headers andlint-imports.fluidCLI, on the requester's scenario:plan --env prodandbundle --env prodpatch onlytransform;backfillandingestare untouched inplan.jsonand incontract.resolved.yaml.overlay_patch_retargetedis printed once per command.validatereportscontract_load_failed, namingbuilds[0].overlay_entry_addednaming the base ids.ripwire --quality-delta: no gating rows.Review verdicts
Documentation
Follow-ups (not in this PR)
$refgives different results per command (plan drops it; bundle merges the raw$ref).datamesh-manager publish --overlay <file>passes the path as an env name.fluid planandfluid applyreport an overlay load failure asplanner_failed/ "Unexpected error", whilevalidatesayscontract_load_failed.validatedoes not reject duplicate build ids.x-kubernetes-list-map-keys) so a later version that adds a keyed list carries it.