refactor: collapse extends' duplicate list helper onto keys.py#56
Merged
Conversation
extends._as_list was byte-identical to keys._as_list -- same body, same error message -- with the first two parameters swapped. Each call site was correct only because it matched its own module's local signature; a transposition would have produced a garbled message and nothing would have caught it. keys.py's _as_list/_concat_list join its public cross-module primitives as as_list/concat_list, on the same (name, key, value) signature as the rest. extends drops its copy and its inline concat. extends._as_mapping stays: it is deliberately stricter than pairs_to_mapping, accepting list form only for environment and depends_on, so collapsing it would silently start coercing list-form extra_hosts. Its parameter order is corrected to match. Behavior-preserving: the emitted script for a document exercising every merge path is byte-identical.
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.
Last of the changes spawned by the audit (finding D4, #52). Design:
planning/changes/2026-07-14.03-extends-keys-dedup.md.The footgun
extends.pycarried a private_as_listthat was byte-identical tokeys.py's_as_list— same body, same error message — with the first two parameters swapped:Each call site was correct only because it happened to match its own module's local signature. A transposition would have rendered
service 'volumes': cannot merge 'app'— names and keys swapped — and nothing would have caught it. That sits in theextendsmerge path, which runs ahead of the gate (cli.pycallsresolve_extends()beforevalidate()) — precisely where this codebase has already been bitten.The change
keys._as_list/_concat_listare promoted to publicas_list/concat_list, joining the module's existing cross-module primitives (key_value_pairs,pairs_to_mapping,validate_map,require_string_keys,extra_host_pairs,is_number) on the same(name, key, value)signature.extends.pydrops its copy and its open-coded concat. One definition, one parameter order.extends._as_mappingdeliberately stays. It is not duplication — it is stricter thanpairs_to_mapping, accepting list form only forenvironmentanddepends_onand refusing it forextra_hosts/healthcheck. Collapsing it would silently start coercing list-formextra_hostson a merged side: a behavior change wearing a dedup's clothes. Its parameter order is corrected to match, which removes the other half of the footgun.No structural-key registry —
decisions/2026-07-12-reject-structural-key-registry.mdstands. This moves two helpers; it does not build a dispatch table.Proof it's behavior-preserving
The emitted script for a document exercising every merge path (mapping-merge, sequence-concat, registry merge, structural concat, override) is byte-identical to
main. Review independently diffedresolve_extends()output across 20 documents covering every merge path and all five error paths — identical on both revisions.Every existing test passes unedited; a pure refactor that needed its expectations changed would not be pure. Two tests added, pinning the
name/keyorder in the merge error so a future transposition fails loudly instead of producing a garbled message.551 tests at 100% coverage.
🤖 Generated with Claude Code