docs(solutions): compound refresh — fix reference drift, grow CONCEPTS.md

Refresh audit of all 13 docs/solutions learnings against the current
codebase. Core guidance verified accurate everywhere; three docs had
reference drift:

- dual-repo-submodule-workflow: point worktree setup/teardown at the
  make worktree-new/worktree-rm targets (manual procedure misses
  .worktree-env isolation)
- docker-exec-root-owned-bind-mount-files: tracked-plugin list now
  includes entry-actions; fix-perms description matches actual target
- grav-plugin-config-without-code-wont-enable: 3-category model's
  custom-in-repo list now includes entry-actions

CONCEPTS.md: add Container, Content repo, Outer repo, Pin, Env tree,
Remote-only plugin; refresh Active Trip (switching is one setting now).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
This commit is contained in:
2026-07-08 23:37:56 +02:00
co-authored by Claude Fable 5
parent f3816bfc3e
commit 24867524a1
4 changed files with 36 additions and 21 deletions
@@ -143,8 +143,8 @@ The reusable principle, worth internalizing beyond this one repo:
- **Don't rely on `APACHE_RUN_USER` or compose-level `UID`/`GID` env vars to fix exec ownership** — they don't apply to `docker exec`. `APACHE_RUN_USER` only affects Apache workers; compose `user:`/env vars only affect services wired to consume them.
- **You can't just add `user:` to a service whose entrypoint needs root** (to bind privileged ports, set up cron, etc.). Drop privileges per-exec instead of per-container.
- **If a tool run as non-root needs writable scratch dirs that are root-owned in the image, chown them container-internally first.** That doesn't touch the host.
- **Root-owned files accumulate invisibly.** (session history) Plugin code under `user/plugins/<name>/` is gitignored by project convention (only `cache-on-save` and `story-blocks` are tracked), so root-owned files pile up in the bind mount without ever appearing in `git status` — they only bite at worktree-removal time. Don't wait for `git status` to reveal them; `find ./user -uid 0 | wc -l` is the real detector.
- **Keep a `make fix-perms` escape hatch** (`find ./user -uid 0 ... chown`) for residual root files — notably first-boot files the base-image entrypoint writes as root (`config/security.yaml`, `data/api-keys.yaml`), which no `-u` on a make target can reach. After this fix it's a rare mop-up, not a routine step.
- **Root-owned files accumulate invisibly.** (session history) Plugin code under `user/plugins/<name>/` is gitignored by project convention (only `cache-on-save`, `story-blocks`, and `entry-actions` are tracked), so root-owned files pile up in the bind mount without ever appearing in `git status` — they only bite at worktree-removal time. Don't wait for `git status` to reveal them; `find ./user -uid 0 | wc -l` is the real detector.
- **Keep a `make fix-perms` escape hatch** (container-internal `chown -R 1000:1000 /var/www/html`) for residual root files — notably first-boot files the base-image entrypoint writes as root (`config/security.yaml`, `data/api-keys.yaml`), which no `-u` on a make target can reach. After this fix it's a rare mop-up, not a routine step.
- **Verification recipe:** `docker exec -u 1000:1000 <container> touch /mnt/f && stat -c '%u' host/f` should print your uid, not `0`.
This lives in the Makefile because make targets are the only sanctioned container interface in this project — the fix belongs there, not in ad-hoc docker commands.
@@ -89,7 +89,7 @@ How the code went missing here is **unconfirmed**. It happened around the git-sy
- **Check the code layer before the config layer.** When a Grav plugin "won't enable" and config toggles do nothing, FIRST verify the code exists: `ls user/plugins/<name>/` on the server. Config-without-code is the failure class; the empty directory is the tell.
- **Enumerate both layers in all locations when diagnosing.** Plugins have a code layer (`user/plugins/<name>/`) and a config layer, and on prod the config can live in the env tree (`user/env/<host>/config/plugins/<name>.yaml`) and persist completely independently of the code. Remember: once `user/env/<host>/` exists, Grav Admin writes ALL config there, so always check both `user/config/...` and the env path (env wins).
- **Know which plugins are remote-only.** The 3-category model: GPM-via-`plugins.txt` (admin2 / api / flex-objects), custom-in-repo (cache-on-save / story-blocks), and remote-only (git-sync — never in `plugins.txt`). Remote-only plugins are NOT restored by the standard install/content flows, so reinstall them explicitly via GPM after any operation that could have wiped `user/plugins/`.
- **Know which plugins are remote-only.** The 3-category model: GPM-via-`plugins.txt` (admin2 / api / flex-objects), custom-in-repo (cache-on-save / story-blocks / entry-actions), and remote-only (git-sync — never in `plugins.txt`). Remote-only plugins are NOT restored by the standard install/content flows, so reinstall them explicitly via GPM after any operation that could have wiped `user/plugins/`.
- **Diagnose actual state before proposing config fixes.** An `ls` is cheaper than a guess. Establishing that the code was missing would have pointed straight at the reinstall instead of a round of config poking.
## Related