Skip to content

feat(loader): overlays patch builds, exposes and consumes by identity (schema 0.7.6) - #732

Merged
fas89 merged 5 commits into
mainfrom
feat/overlay-keyed-merge
Oct 19, 2026
Merged

fas89 merged 5 commits into
mainfrom
feat/overlay-keyed-merge

Conversation

@fas89

@fas89 fas89 commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

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 has builds: [ingest, transform], and overlays/prod.yaml raises builds[1], meaning transform. Add a backfill build first, and prod now patches ingest. It also copies id: transform onto it, and fluid plan --env prod fails with planner_failed: 'schedule_transform', with no word of why (reproduced on main).

This PR does three things.

From fluidVersion 0.7.6 (the schema preview), overlays merge by identity.

  • The keys are builds[].id, exposes[].exposeId, and consumes[] by productId + exposeId. They match an overlay entry to its base entry when the base's entries carry unique ones.
  • An entry that matches nothing is appended and logged as overlay_entry_added. The warning names the base's ids, so a typo shows.
  • An overlay entry the merge cannot match (no id, or an id named twice) fails the load with its index. Falling back to position would retarget it, which is the failure this fixes. fluid validate --env and load_with_overlay report it with the overlay's path, fluid bundle --env does too, and the contract API raises contract_load_failed. Id-less patches still apply to a base with a single entry.
  • A base whose own ids are missing or repeated keeps the positional merge and logs overlay_merged_by_position.
  • Only the three top-level lists are keyed. Lists inside an entry, such as an expose's schema, still merge by position.

Older contracts merge exactly as before, and now warn. overlay_patch_retargeted fires when a positional patch lands on an entry that names another id, or another part of a consumes key. It names both ids, the fix, and the opt-in.

One chokepoint. The rule sits in _deep_merge, so every overlay path uses it:

  • the loader: validate, plan, apply, diff, verify, publish;
  • fluid bundle --env and generate artifacts;
  • the contract API.

Each warning names its contract and is logged once per process (apply loads its contract twice), as note_missing_overlay already 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; --strict does not see loader warnings today.

Borrowed, not built

  • Kubernetes server-side apply list-map keys (x-kubernetes-list-map-keys, compound keys such as containerPort + 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.
  • jsonmerge 1.9.2 arrayMergeById (MIT): an id read from the entry, with compound ids.
  • Strategy: adapt the pattern. The keyed merge is a few dozen lines, and jsonmerge would bring jsonschema onto the light CLI's cold path, so we borrow the semantics, not the dependency.

Validation

  • Tests: tests/test_overlay_keyed_merge.py (28 tests, several parametrized). The 9 original behaviour tests fail on main.
  • Mutation, per the test audit: all killed:
    • always or never keyed;
    • no append;
    • the len(base) > 1 guard;
    • first key field only;
    • no retarget warning;
    • no merged-by-position warning;
    • version read from the base only;
    • overlay ids not unique.
  • Full suite: 26,600 passed; the 6 failures are the environmental baseline that reproduces on main. Perf startup budget: 38 passed. Lint and format: whole-tree ruff, black 24.10.0, the dead-code gate, license headers and lint-imports.
  • Live through the fluid CLI, on the requester's scenario:
    • 0.7.6: plan --env prod and bundle --env prod patch only transform; backfill and ingest are untouched in plan.json and in contract.resolved.yaml.
    • 0.7.5: the merge is unchanged, and overlay_patch_retargeted is printed once per command.
    • An id-less overlay on 0.7.6: validate reports contract_load_failed, naming builds[0].
    • A typo'd id: appended with overlay_entry_added naming the base ids.
  • ripwire --quality-delta: no gating rows.

Review verdicts

  • security-review (three rounds): no findings. The keyed merge narrows where a patch lands (the ids must be equal). Merged ids stay unique. Every caller propagates the new error, so nothing fails open. The logs carry ids only.
  • world-class-inspection: documentation, architecture, codebase and test suite reported CONCERNS in round 1. Every change-caused finding is fixed and re-verified:
    • the keyed fallback that retargeted;
    • the wrong remedy text;
    • the silent append;
    • the attribution;
    • the tests for later versions;
    • the API error type;
    • the per-contract warnings;
    • the bundle file name.
  • defect-scan (hunter plus two re-verify rounds): all change-caused findings fixed. Pre-existing ones are follow-ups below.

Documentation

Follow-ups (not in this PR)

  • An overlay holding a $ref gives 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 plan and fluid apply report an overlay load failure as planner_failed / "Unexpected error", while validate says contract_load_failed.
  • validate does not reject duplicate build ids.
  • Declare merge keys per list in the schema (like x-kubernetes-list-map-keys) so a later version that adds a keyed list carries it.

fas89 added 2 commits October 19, 2026 16:42
… 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.
@github-actions github-actions Bot added cli Changes to the CLI surface or implementation tests Test coverage or test infrastructure changes needs-docs Pull request needs a linked docs update or justification labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown

📄 Documentation Reminder

This PR appears to be missing a documentation reference. Our docs live in a separate repo.

Please update the PR description with one of:

  • Link a docs PR — check the "Docs PR linked" box and paste the URL
  • Mark as no docs needed — check "No docs needed" with a justification
  • Acknowledge docs TODO — check "Docs TODO" and create the docs PR before merge

See the Contributing Guide for details.

@github-actions github-actions Bot removed the needs-docs Pull request needs a linked docs update or justification label Oct 19, 2026
Comment thread tests/test_overlay_keyed_merge.py Fixed
fas89 added 3 commits October 19, 2026 17:16
…ader

The overlay tests imported fluid_build.loader both as a module and by
name (code-quality review on #732).
@fas89
fas89 merged commit 185627d into main Oct 19, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Changes to the CLI surface or implementation tests Test coverage or test infrastructure changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant