diff --git a/BUILD-LOG.md b/BUILD-LOG.md index c91754d4..5ff4c6f9 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -106,4 +106,35 @@ No credentials are recorded in this file. All 11 acceptance criteria demonstrated. The real model request passed. +--- + +## Phase 5: Configuration-driven Hello World (M1) + +### Entry 5.1 — before + +- Timestamp: 2026-09-03 +- Intended action: Make the container POC configuration-driven. Baseline committed and tagged `poc-container-hello-v0`. Milestone M1 tracked in Gitea (issues #1-#4): (T1) config module with idempotent bootstrap and strict v1 validation; (T2) wire scripts and compose to config.json with fail-closed behavior; (T3) sandboxed config selftests; (T4) E2E verification and documentation. +- Reason: Per docs/plans/2026-09-02_atomic-mosaic-foundation.md — config.json must be the sole discovery entry point; updates and runs must never corrupt or invent configuration. +- Expected result: All M1 acceptance criteria pass; Hello World reproducible from configuration alone. + +### Entry 5.2 — after + +- Timestamp: 2026-09-03 +- Commands run: `scripts/test-config.sh` (20 cases); fail-closed checks (compose without launcher env, verify/reset with missing config); `scripts/bootstrap.sh`; config-driven `scripts/hello.sh`, `scripts/verify.sh`, negative marker test, sandboxed reset symlink refusal (canary survived), real reset + bootstrap + build + verify; config checksum comparison across the entire flow. +- Observed result: + - Config selftests: 20 passed, 0 failed. + - Fail-closed confirmed: compose exits 1 without launcher env; verify/reset exit 1 on missing config before any mutation. + - Bootstrap created `~/.config/mosaic-dev/config.json` exclusively; second run validated without rewriting (content + mtime unchanged). + - Config-driven hello/verify returned exactly `MOSAIC_HELLO_OK`; verify exit 0; negative marker test exit 1. + - Reset refused symlinked dataRoot; canary file survived; real reset removed only the configured data root. + - config.json checksum unchanged across hello/verify/reset/bootstrap/build/verify. +- Failure or correction: + 1. Selftest harness bug: `cfg` helper invoked without a body for the symlink case (`$2: unbound variable`). Fixed in the harness; product code unaffected. + 2. E2E rerun-after-reset failure: `verify.sh` did not ensure the configured data root existed before the container mount. With the data root absent, Docker auto-created the host path as root:root, and the container's uid-1000 user could not write the generated system prompt. Fixed by calling `bootstrap_runtime_dir` in `verify.sh`; also hardened it to fail with a clear message when the data root exists but is not writable (root-owned leftover). Clean-slate E2E rerun: all steps green. +- Credential check: no credential material in config, scripts, logs, or test output. + +## Result (M1) + +Configuration-driven Hello World verified. `main` merged with M1 and tagged `config-hello-v1`. + diff --git a/README.md b/README.md index a79332db..3c062c92 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,65 @@ to return exactly `MOSAIC_HELLO_OK`. ## Layout ```text -BRIEF.md requirements for this experiment +BRIEF.md requirements for the original container proof BUILD-LOG.md append-only build/verification log LAYERS.md implemented layer (L0) and deferred layers (L1-L6) Containerfile image definition (node:24-bookworm-slim, non-root, pinned Pi) -compose.yaml one service: mosaic-agent (one-shot) +compose.yaml one service: mosaic-agent (one-shot; configured via env) package.json pins @earendil-works/pi-coding-agent at exactly 0.84.4 package-lock.json resolved lockfile used by npm ci in the image -.env.example non-secret settings only (provider, model) +.env.example non-secret settings only (credential-file path, env-var auth) contracts/ CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md (immutable fixtures) -scripts/ build.sh, hello.sh, verify.sh, reset.sh (+ shared common.sh) +scripts/ bootstrap/build/hello/verify/reset + config tooling src/ load-contracts.sh, run-agent.sh (run inside the container) +docs/plans/ architecture and milestone plans ``` +## Configuration + +The sole discovery entry point is: + +```text +~/.config/mosaic-dev/config.json +``` + +Created only by the explicit, idempotent bootstrap: + +```bash +scripts/bootstrap.sh # create-if-absent; validates existing config, never rewrites +``` + +Minimal shape (`configVersion` 1): + +```json +{ + "configVersion": 1, + "environment": "development", + "dataRoot": "/home/jwoltje/.mosaic-dev", + "execution": { + "backend": "docker", + "provider": "zai", + "model": "glm-5.3-flash" + } +} +``` + +Rules enforced by `scripts/mosaic-config.mjs`: + +- Unknown keys, unsupported versions/backends, and malformed JSON exit nonzero; nothing is modified. +- `dataRoot` must be absolute, canonical, and must not be or contain the home or configuration directory. +- Validation failures never touch config, state, or images. +- `scripts/test-config.sh` runs the sandboxed config selftests (no Docker required). + +Run paths (`build/hello/verify/reset`) fail closed when configuration is missing or invalid; they never invent it. + +See `docs/plans/2026-09-02_atomic-mosaic-foundation.md` for the full plan. + Inside the container: ```text /opt/mosaic/contracts immutable contract files -/var/lib/mosaic generated runtime state (mounted from /home/jwoltje/.mosaic-dev) +/var/lib/mosaic generated runtime state (mounted from configured dataRoot) /workspace agent workspace ``` @@ -48,10 +89,12 @@ Inside the container: ## Usage ```bash -scripts/build.sh # build the image -scripts/hello.sh # one-shot request; prints the model response -scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK -scripts/reset.sh # delete /home/jwoltje/.mosaic-dev (safety-checked) +scripts/bootstrap.sh # create config.json if absent (idempotent) +scripts/build.sh # build the image +scripts/hello.sh # one-shot request; prints the model response +scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK +scripts/test-config.sh # fast config-layer selftests (no Docker) +scripts/reset.sh # delete the configured data root (safety-checked) ``` Prove the failure path (acceptance criterion 9): diff --git a/scripts/common.sh b/scripts/common.sh index 10e670b8..e3e21abd 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -23,7 +23,14 @@ load_config() { # project's ownership marker. The marker is what scripts/reset.sh requires # before it will delete anything. bootstrap_runtime_dir() { - if [ ! -d "$MOSAIC_DEV_DIR" ]; then + if [ -d "$MOSAIC_DEV_DIR" ]; then + if [ ! -w "$MOSAIC_DEV_DIR" ] || [ ! -x "$MOSAIC_DEV_DIR" ]; then + echo "bootstrap: $MOSAIC_DEV_DIR exists but is not writable by $(id -un)" >&2 + echo "bootstrap: a root-owned directory here is usually leftover from a" >&2 + echo "bootstrap: Docker-created mount source; remove it and rerun." >&2 + exit 1 + fi + else mkdir -p "$MOSAIC_DEV_DIR" echo "bootstrap: created $MOSAIC_DEV_DIR" fi diff --git a/scripts/verify.sh b/scripts/verify.sh index 52b1656b..9a3384d7 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -19,6 +19,10 @@ EXPECTED="${EXPECTED_MARKER:-MOSAIC_HELLO_OK}" load_config +# Ensure the configured data root exists (host-owned) before the mount, +# otherwise Docker would auto-create a root-owned directory. +bootstrap_runtime_dir + # 1. Build the image only if it is not already present. if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then echo "verify: image $IMAGE not found, building..." >&2