Summary
The README states that inputs are "merged deterministically", and they are — but
precedence is carried entirely by the order of --ssrf-root CLI arguments.
No root declares its own position in the merge, so correctness lives in the
invocation rather than in the data.
This works today because there is exactly one overlay and the documented command
happens to be correct. It becomes a real hazard as soon as there are two
overlays, or a second person runs the build.
Reproduction
Documented order (authoritative first, overlay last) — the asg_wx1 patch applies:
$ codeplugger-profile profiles/baofeng_dm32/reference.yml \
--ssrf-root ../ssrf-lite/ssrf \
--ssrf-root ../chioff-ssrf-private/ssrf
...
notes: Synthetic note applied by the public reference overlay.
Reversed:
$ codeplugger-profile profiles/baofeng_dm32/reference.yml \
--ssrf-root ../chioff-ssrf-private/ssrf \
--ssrf-root ../ssrf-lite/ssrf
error: ../chioff-ssrf-private/ssrf/overrides/reference_weather.yml:
unknown assignments override target 'asg_wx1'
What is already good
The failure above is correct behavior, and worth preserving. In
ssrf/overlays.py, override targets are filtered to strictly earlier roots:
targets = [
target
for target in index.get((collection, entity_id), [])
if target[2] < root_index
]
So an overlay cannot patch a record that has not been loaded yet, and ambiguous
targets raise rather than silently picking one. That is the right model. The gap
is not the merge semantics — it is that nothing binds a root to its intended
precedence.
The gap
Two failure modes that the current design cannot catch:
-
Silent wrong-order resolution between overlays. If two overlays both patch
the same ID, whichever is passed later wins. Both orders resolve without error
and produce different codeplugs. There is no signal that anything is wrong.
-
Additive-only overlays reorder silently. The root_index guard only fires
for overrides: blocks. An overlay that only adds entities (like
reference_simplex.yml) resolves fine in any position, so a mis-ordered
invocation is not necessarily caught by the existing check.
The practical risk: a generated codeplug is a build artifact, and the README is
explicit that it must not become an authoritative source. But if the artifact
depends on argv ordering, then reproducing a build requires knowing the exact
command someone typed, which is not recorded anywhere in the data.
Proposed direction
Let each root declare its own precedence, and have the resolver sort rather than
trust argv. Rough shape — naming open for discussion:
# ssrf/_root.yml (or a top-level key in existing documents)
ssrf_root:
id: "chioff_reference_overlay"
precedence: 100 # higher wins; authoritative data is 0
Then:
resolve_ssrf_roots() sorts by declared precedence, ignoring argument order.
- Duplicate precedence values across roots raise, rather than falling back to argv.
- A root with no declaration keeps today's positional behavior, so this stays
backward compatible.
Alternative if a manifest feels heavy: keep argv ordering but require roots to be
named (--ssrf-root authoritative=../ssrf-lite/ssrf) and have profiles declare
which named roots they expect. That moves the contract into the profile instead
of the root.
Either way the goal is the same: a mis-ordered build should be impossible, not
merely detectable in one direction.
Scope note
This is a ssrf-lite change more than a codeplugger one — resolve_ssrf_roots
lives in ssrf/overlays.py. Filing here because codeplugger is the consumer
that exposes the ordering to users via CLI flags, and because the fix should be
designed against how profiles actually get built. Happy to move it if you would
rather track it upstream.
Low urgency while there is one overlay. Worth settling before the pattern
propagates to real user repos, since username-ssrf-private plus a club or
regional overlay is exactly the two-overlay case above.
Summary
The README states that inputs are "merged deterministically", and they are — but
precedence is carried entirely by the order of
--ssrf-rootCLI arguments.No root declares its own position in the merge, so correctness lives in the
invocation rather than in the data.
This works today because there is exactly one overlay and the documented command
happens to be correct. It becomes a real hazard as soon as there are two
overlays, or a second person runs the build.
Reproduction
Documented order (authoritative first, overlay last) — the
asg_wx1patch applies:Reversed:
What is already good
The failure above is correct behavior, and worth preserving. In
ssrf/overlays.py, override targets are filtered to strictly earlier roots:So an overlay cannot patch a record that has not been loaded yet, and ambiguous
targets raise rather than silently picking one. That is the right model. The gap
is not the merge semantics — it is that nothing binds a root to its intended
precedence.
The gap
Two failure modes that the current design cannot catch:
Silent wrong-order resolution between overlays. If two overlays both patch
the same ID, whichever is passed later wins. Both orders resolve without error
and produce different codeplugs. There is no signal that anything is wrong.
Additive-only overlays reorder silently. The
root_indexguard only firesfor
overrides:blocks. An overlay that only adds entities (likereference_simplex.yml) resolves fine in any position, so a mis-orderedinvocation is not necessarily caught by the existing check.
The practical risk: a generated codeplug is a build artifact, and the README is
explicit that it must not become an authoritative source. But if the artifact
depends on argv ordering, then reproducing a build requires knowing the exact
command someone typed, which is not recorded anywhere in the data.
Proposed direction
Let each root declare its own precedence, and have the resolver sort rather than
trust argv. Rough shape — naming open for discussion:
Then:
resolve_ssrf_roots()sorts by declared precedence, ignoring argument order.backward compatible.
Alternative if a manifest feels heavy: keep argv ordering but require roots to be
named (
--ssrf-root authoritative=../ssrf-lite/ssrf) and have profiles declarewhich named roots they expect. That moves the contract into the profile instead
of the root.
Either way the goal is the same: a mis-ordered build should be impossible, not
merely detectable in one direction.
Scope note
This is a
ssrf-litechange more than acodepluggerone —resolve_ssrf_rootslives in
ssrf/overlays.py. Filing here becausecodepluggeris the consumerthat exposes the ordering to users via CLI flags, and because the fix should be
designed against how profiles actually get built. Happy to move it if you would
rather track it upstream.
Low urgency while there is one overlay. Worth settling before the pattern
propagates to real user repos, since
username-ssrf-privateplus a club orregional overlay is exactly the two-overlay case above.