# Task 2 report — portable Compose contract ## Status Completed. The root Compose file is now a portable two-service base, with explicit local and server overrides. Workspace-registry configuration remains generic and continues to require an installation-provided Git remote. ## Changed files - `.env.example` — shared non-secret, generic endpoint and registry examples. - `compose.yaml` — portable `core` and `frontend` base, owned `thothii` network, health checks, named settings/Pi/registry/session volumes, and generic external endpoint variables. - `deploy/compose.local.yaml` — loopback core/frontend ports, `AUTH_MODE=none`, local installation identity, and non-persistent restart policy. - `deploy/compose.server.yaml` — frontend-only configurable host bind, `AUTH_MODE=upstream`, server identity, host-root data/Pi/registry mounts, and restart policy. - `deploy/env/local.env.example` and `deploy/env/server.env.example` — safe profile-specific values using `.example.invalid` documentation domains. - `scripts/test-default-compose.sh` — default local portable Compose assertion. - `scripts/test-unified-compose.sh` — JSON structural assertions for base/local/server plus the required missing-remote failure checks. ## RED evidence Before changing Compose, `bash scripts/test-unified-compose.sh` exited 1 with: ```text Error: forbidden application coupling ``` The failure was raised by the required assertion while the rendered root config still contained the portal-specific networks and host paths. ## GREEN evidence The following fresh commands completed with exit status 0: ```text bash scripts/test-default-compose.sh # default Compose contract passed. bash scripts/test-unified-compose.sh # unified Compose contract passed. bash -lc 'set -a; source deploy/env/local.env.example; set +a; docker compose -f compose.yaml -f deploy/compose.local.yaml config --quiet' bash -lc 'set -a; source deploy/env/server.env.example; set +a; docker compose -f compose.yaml -f deploy/compose.server.yaml config --quiet' bash scripts/verify-line-endings.sh git diff --check ``` The two `config --quiet` invocations source only their non-secret profile example so the requested commands can validate the required Git-remote interpolation without an operator `.env` file. ## Self-review - The base renders exactly `core` and `frontend`; it has no portal, Chirone, local-LLM-network, or host-path coupling. - Local publishing is loopback-only; server has no core host port and publishes the frontend bind. - The core retains outbound access for configured external DWH, vector, Git, embedding, and LLM endpoints; the owned Compose network is private to this stack but is not marked Docker-internal. - The structural test validates the exact required service assertion, forbidden-coupling assertion, required base network/volumes, profile port policy, and `THT_WORKSPACE_GIT_REMOTE` failure. - All new YAML and shell files were checked by the repository line-ending verifier. ## Commit `2595d35682a5200b877acb71764b4eb195a39ea4` — `deploy: unify local and server compose stack` ## Concerns None for Task 2. Git and connector secret transport remains intentionally outside this base/profile contract and is addressed by the next scoped task. --- ## Fix round 1/5 — Pi auth mount and generic LLM endpoint ### Status Completed. Pi’s mutable state remains writable, while provider authentication is supplied only by the deterministic `PI_AUTH_FILE` host-file contract mounted read-only at Pi’s canonical `/home/thoth/.pi/agent/auth.json` path. The base now exposes the generic `THT_LLM_URL` contract; the local and server examples set only documentation endpoint values. ### Changed files - `compose.yaml` — adds `THT_LLM_URL` and a read-only `PI_AUTH_FILE` bind while retaining the writable `pi-state` volume for non-secret runtime state. - `deploy/compose.server.yaml` — retains the auth-file bind when its storage mounts override the portable base. - `.env.example`, `deploy/env/local.env.example`, and `deploy/env/server.env.example` — document the generic `https://llm.example.invalid` endpoint; profile examples also document the required host auth-file path without including a secret. - `scripts/test-unified-compose.sh` — asserts generic LLM contract presence, no Docker socket/daemon mount, and exactly one read-only Pi auth file bind in the base and both profiles. ### RED evidence Before the Compose changes, this command exited 1: ```text bash scripts/test-unified-compose.sh Error: core must expose a generic THT_LLM_URL endpoint contract ``` The failure was raised by the new structural assertion against the previous rendered base. ### GREEN evidence The following focused commands completed with exit status 0 after the fix: ```text bash scripts/test-unified-compose.sh # unified Compose contract passed. bash scripts/test-default-compose.sh # default Compose contract passed. docker compose --env-file deploy/env/local.env.example -f compose.yaml -f deploy/compose.local.yaml config --quiet docker compose --env-file deploy/env/server.env.example -f compose.yaml -f deploy/compose.server.yaml config --quiet bash scripts/verify-line-endings.sh git diff --check ``` ### Self-review - Both rendered profile contracts carry a single `bind` mount to the Pi `auth.json` file with `read_only: true`; no auth value appears in Compose or the environment examples. - The writable Pi-state mount does not replace the read-only auth file, and the server override explicitly retains that file bind after replacing the base storage list. - The structural regression test rejects any Docker socket/daemon mount and validates the generic LLM endpoint contract without adding provider-specific endpoints or hostnames. - The deferred Minor report-wording finding was not changed. ### Commit `2ce2e0089a66ca508db041a71db9807f66d0bf24` — `fix: harden compose Pi auth mounts` ### Concerns None for this focused round.