Skip to content

Commit 552eece

Browse files
committed
docs(planning): fix stale merge signature and structural-key count in design doc
Final review caught two drift spots in the design file: illustrative code snippets still showed the 2-arg merge(base, local) signature superseded during implementation by the 4-arg (name, key, base, local) form, and the structural-key count said 8 instead of 9.
1 parent 25238f8 commit 552eece

1 file changed

Lines changed: 12 additions & 9 deletions

File tree

planning/changes/2026-07-13.05-derive-extends-merge-policy-from-keyspec.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Add a `merge: Callable[[Any, Any], Any] | None` field to `KeySpec`. The
1111
respectively), so every `SERVICE_KEYS` entry built through them gets extends
1212
support for free. `extends.py`'s `_merge` looks up `SERVICE_KEYS[key].merge`
1313
first, falling back to a shrunk, structural-only `_MERGE_KEYS`/`_CONCAT_KEYS` for
14-
the 8 keys that still have no `KeySpec`. A new `_pairs_to_mapping` in `keys.py`
14+
the 9 keys that still have no `KeySpec`. A new `_pairs_to_mapping` in `keys.py`
1515
(the inverse of `_key_value_pairs`) backs the map-merge callable and, as a side
1616
effect, fixes list-form `labels`/`annotations` merging — currently untested and
1717
currently broken.
@@ -46,7 +46,7 @@ for `labels`/`annotations` (that's what `_key_value_pairs` exists for), but
4646
class KeySpec:
4747
validate: Callable[[str, str, Any], None]
4848
emit: Callable[[Any], list[Token]]
49-
merge: Callable[[Any, Any], Any] | None = None
49+
merge: Callable[[str, str, Any, Any], Any] | None = None
5050
```
5151

5252
`_list()` and `_map()` set it automatically — no opt-in argument, so a future
@@ -62,11 +62,14 @@ def _map(flag: str) -> KeySpec:
6262
return KeySpec(validate=_validate_map, emit=emit, merge=_merge_map)
6363
```
6464

65-
`_concat_list(base, local)` normalizes both sides via the existing
65+
`_concat_list(name, key, base, local)` normalizes both sides via the existing
6666
list-or-str handling (moved from `extends._as_list`) and concatenates.
67-
`_merge_map(base, local)` normalizes both sides via the new `_pairs_to_mapping`
68-
(list-or-dict → dict, the inverse of `_key_value_pairs`, living beside it in
69-
`keys.py`) and dict-merges, local winning. `ulimits`'s bespoke `KeySpec`
67+
`_merge_map(name, key, base, local)` normalizes both sides via the new
68+
`_pairs_to_mapping` (list-or-dict → dict, the inverse of `_key_value_pairs`,
69+
living beside it in `keys.py`) and dict-merges, local winning. `merge`'s
70+
`(name, key, ...)` prefix mirrors `validate`'s signature, so a merge callable
71+
can raise the same service/key-named `UnsupportedComposeError` as validation
72+
does. `ulimits`'s bespoke `KeySpec`
7073
(`keys.py:191`) sets `merge=_merge_map` explicitly since it bypasses the `_map()`
7174
factory.
7275

@@ -78,7 +81,7 @@ def _merge(base: dict[str, Any], local: dict[str, Any], name: str) -> dict[str,
7881
for key, local_val in local.items():
7982
spec = SERVICE_KEYS.get(key)
8083
if key in base and spec is not None and spec.merge is not None:
81-
merged[key] = spec.merge(base[key], local_val)
84+
merged[key] = spec.merge(name, key, base[key], local_val)
8285
elif key in base and key in _MERGE_KEYS: # structural keys only
8386
...
8487
elif key in base and key in _CONCAT_KEYS: # structural keys only
@@ -88,7 +91,7 @@ def _merge(base: dict[str, Any], local: dict[str, Any], name: str) -> dict[str,
8891
return merged
8992
```
9093

91-
`_MERGE_KEYS`/`_CONCAT_KEYS` shrink to the 8 keys with no `KeySpec`:
94+
`_MERGE_KEYS`/`_CONCAT_KEYS` shrink to the 9 keys with no `KeySpec`:
9295
`_MERGE_KEYS = {"environment", "extra_hosts", "healthcheck", "depends_on"}`,
9396
`_CONCAT_KEYS = {"secrets", "configs", "volumes", "tmpfs", "env_file"}` — their
9497
`_as_mapping`/`_as_list` handling is unchanged. A one-line comment marks these
@@ -102,7 +105,7 @@ and shape-derived.
102105

103106
## Non-goals
104107

105-
- Unifying the 8 structural keys' merge policy — deferred; would conflate this
108+
- Unifying the 9 structural keys' merge policy — deferred; would conflate this
106109
change with candidate #3 of the 2026-07-13 architecture review (owning
107110
module per structural key), and `decisions/2026-07-12-reject-structural-key-registry.md`
108111
already rejects a uniform structural registry on shape-heterogeneity grounds

0 commit comments

Comments
 (0)