Files
headquarter/openspec/specs/mount-specificity-ordering.md
T
Alex Blank 0952aa8217 fix: sort mount volumes by specificity to prevent parent mounts hiding children
When git repo mounts and regular file mounts have overlapping target
paths, broader parent mounts hide deeper child mounts because Docker
Compose applies volumes in array order.

- Add sort_volumes_by_specificity() to docker.py:
  - Sorts by target path depth (parent paths first, child paths last)
  - Logs warnings for duplicate targets
  - Handles :bind and :ro suffixes correctly

- Integrate into manifest flow (compile_compose):
  - Sorts manifest mounts + EXTRA_VOLUMES before writing compose

- Integrate into legacy flow (_modify_compose_file):
  - Sorts after appending extra_volumes to existing template volumes

- Add 6 unit tests covering parent/child ordering, stable sort,
  type suffixes, empty list, single volume, and duplicate warnings.

Quality gates: pytest (214 passed, 6 pre-existing), tsc --noEmit (clean)
2026-05-29 11:35:27 +02:00

46 lines
1.7 KiB
Markdown

# Spec: Mount Specificity Ordering
## Requirements
1. All volume mount entries written to compose files must be sorted by target path depth.
2. Shorter / parent target paths appear **before** deeper / child target paths.
3. Deeper mounts are applied later by Docker, overlaying parent mounts correctly.
4. Exact same-target overlaps are logged as warnings.
5. Works for both manifest and legacy compose generation paths.
## Algorithm
```python
def sort_volumes_by_specificity(volumes: list[str]) -> list[str]:
"""
Sort volume strings so parent paths come before child paths.
Volume format: source:target or source:target:type
"""
```
1. Parse each volume string to extract the target path (second colon-delimited field).
2. Normalize the target: strip trailing `/`, collapse `//`.
3. Compute depth = number of `/`-separated segments.
4. Sort ascending by depth. Stable sort preserves input order for equal depths.
5. Detect exact same-target strings and log warnings.
## Compose Format Handling
- `source:target` → target is second field
- `source:target:bind` → target is second field
- `source:target:ro` → target is second field
- Split by `:` into at most 3 parts. Target is always index 1.
## Integration Points
- `compile_compose()` in `manifest_compiler.py`: sort `volumes` list before assigning to `service["volumes"]`.
- `_modify_compose_file()` in `tool_instances.py`: sort `service_config["volumes"]` after appending extra volumes.
## Tests
- Repo mount `/workspace/x` + file mount `/workspace/x/y/config.json` → file mount comes after.
- Same depth mounts → stable order preserved.
- Exact same target → warning logged.
- Empty volumes list → no-op.
- Volume with `type` suffix → parsed correctly.