fix: stage config-profile mounts as directories so ~/.config stays writable

- Switch apply_resolved_profile from per-file bind mounts to one
  directory-level bind mount per ResolvedMount target.
- Stage all configured files under instance_dir/mounts/<sanitized_target>
  and bind-mount that directory, so Docker no longer creates a root-owned
  parent directory such as ~/.config.
- Propagate read-only mode ('ro') as the 'readonly' flag on volume entries.
- Update unit tests to expect directory-level mounts and add coverage for
  readonly/writable flags.

Quality gates: python3 -m py_compile, pytest (313 passed, 34 skipped),
npm run typecheck, npm run lint.
This commit is contained in:
Developer
2026-06-13 11:56:48 +00:00
parent d78ca8a9d5
commit d395aaf574
21 changed files with 206 additions and 53 deletions
@@ -0,0 +1,4 @@
name: config-profile-directory-mounts
status: completed
completed_at: 2026-06-13
started_at: 2026-06-13
@@ -0,0 +1,34 @@
# . (index)
dir: .
## Project Map Protocol
1. Read this protocol and the root `.pi-map.index.md` first.
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
3. Load indexes before rich maps during task-start navigation.
4. Read the local rich map and actual source before editing.
5. Treat non-empty `## dirty` sections in either artifact as stale.
6. If source and generated artifacts disagree, trust source.
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
8. After editing source, run `project_map_patch` for each changed file.
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
Trust boundary: index routes, map orients, source decides.
## role
Provides configuration and documentation for a mount staging system that resolves file ownership issues in containerized environments.
## parent
-
## children
-
## files
- .openspec.yaml
- proposal.md
- tasks.md
## links
index: ./.pi-map.index.md
map: ./.pi-map.md
## workflows
-
## dirty
-
@@ -0,0 +1,35 @@
# .
dir: .
index: ./.pi-map.index.md
## Project Map Protocol
1. Read this protocol and the root `.pi-map.index.md` first.
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
3. Load indexes before rich maps during task-start navigation.
4. Read the local rich map and actual source before editing.
5. Treat non-empty `## dirty` sections in either artifact as stale.
6. If source and generated artifacts disagree, trust source.
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
8. After editing source, run `project_map_patch` for each changed file.
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
Trust boundary: index routes, map orients, source decides.
## role
Provides configuration and documentation for a mount staging system that resolves file ownership issues in containerized environments.
## files
- .openspec.yaml | Defines an OpenSpec configuration profile for directory mounts with completion tracking metadata
- proposal.md | Proposes a design change to fix root-owned directory issues in config profile mounts by staging and bind-mounting entire directories instead of individual files. | dep: Docker, config_profile_resolver.py, test_config_profile_resolver.py
- tasks.md | Tracks completed development tasks for refactoring a mount staging system and running quality gates.
## arch
Configuration-driven design using YAML profiles for directory mounts, with a proposal-based development approach documenting a shift from file-level to directory-level bind-mount staging.
## tags
tasks, profile, directory, mounts, .openspec, staging, py, proposal
## symbols
-
## workflows
-
## dirty
-
@@ -0,0 +1,21 @@
# Config profile directory mounts
## Problem
`apply_resolved_profile` currently stages each config-profile mount file individually and bind-mounts each file into the container. Docker creates the parent directory (for example `~/.config`) as root-owned when only files are mounted, so applications running as a non-root container user cannot write new files inside that directory.
## Decision
Stage a directory per mount target and bind-mount the whole directory. The staged directory is created under `instance_dir/mounts/` and is chowned to the resolved container user before the container starts, so the container user can create and modify files inside the target directory.
## Scope
- Change `apply_resolved_profile` in `apps/api/src/services/config/config_profile_resolver.py` to emit one directory-level volume mount per `ResolvedMount` target.
- Continue staging all configured files under that directory.
- Preserve read-only mode by setting `readonly: true` on the volume entry when `mode` is `ro`.
- Update unit tests in `apps/api/tests/unit/test_config_profile_resolver.py` to expect directory-level mounts.
## Non-goals
- No changes to git-mount, SSH-key, workspace, or manifest mount handling.
- No changes to config profile storage or resolution semantics.
@@ -0,0 +1,8 @@
# Tasks
- [x] Identify the per-file mount staging in `apply_resolved_profile` as the root cause.
- [x] Refactor `apply_resolved_profile` to stage one directory per mount target and bind-mount the directory.
- [x] Propagate read-only mode to the volume entry.
- [x] Update `TestApplyResolvedProfile` unit tests to assert directory-level mounts.
- [x] Run backend quality gates (`py_compile`, `pytest`).
- [x] Commit, merge to `dev`, push.