fix(launcher): ensure configured data root before verify mount; docs for M1 (#4)
- verify.sh now calls bootstrap_runtime_dir after load_config; previously a reset-then-verify flow let Docker auto-create a root-owned mount source - common.sh: fail with clear guidance when data root exists but is not writable - README: configuration section, bootstrap usage, selftest entry point - BUILD-LOG: Phase 5 entries with corrections E2E (clean slate): 20/20 selftests; bootstrap idempotent; config-driven hello/verify MOSAIC_HELLO_OK; negative marker exit 1; reset + rerun green; config checksum unchanged across the entire flow. Closes #4
This commit is contained in:
@@ -106,4 +106,35 @@ No credentials are recorded in this file.
|
|||||||
|
|
||||||
All 11 acceptance criteria demonstrated. The real model request passed.
|
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`.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,24 +9,65 @@ to return exactly `MOSAIC_HELLO_OK`.
|
|||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
BRIEF.md requirements for this experiment
|
BRIEF.md requirements for the original container proof
|
||||||
BUILD-LOG.md append-only build/verification log
|
BUILD-LOG.md append-only build/verification log
|
||||||
LAYERS.md implemented layer (L0) and deferred layers (L1-L6)
|
LAYERS.md implemented layer (L0) and deferred layers (L1-L6)
|
||||||
Containerfile image definition (node:24-bookworm-slim, non-root, pinned Pi)
|
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.json pins @earendil-works/pi-coding-agent at exactly 0.84.4
|
||||||
package-lock.json resolved lockfile used by npm ci in the image
|
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)
|
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)
|
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:
|
Inside the container:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/opt/mosaic/contracts immutable contract files
|
/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
|
/workspace agent workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,10 +89,12 @@ Inside the container:
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/build.sh # build the image
|
scripts/bootstrap.sh # create config.json if absent (idempotent)
|
||||||
scripts/hello.sh # one-shot request; prints the model response
|
scripts/build.sh # build the image
|
||||||
scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK
|
scripts/hello.sh # one-shot request; prints the model response
|
||||||
scripts/reset.sh # delete /home/jwoltje/.mosaic-dev (safety-checked)
|
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):
|
Prove the failure path (acceptance criterion 9):
|
||||||
|
|||||||
+8
-1
@@ -23,7 +23,14 @@ load_config() {
|
|||||||
# project's ownership marker. The marker is what scripts/reset.sh requires
|
# project's ownership marker. The marker is what scripts/reset.sh requires
|
||||||
# before it will delete anything.
|
# before it will delete anything.
|
||||||
bootstrap_runtime_dir() {
|
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"
|
mkdir -p "$MOSAIC_DEV_DIR"
|
||||||
echo "bootstrap: created $MOSAIC_DEV_DIR"
|
echo "bootstrap: created $MOSAIC_DEV_DIR"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ EXPECTED="${EXPECTED_MARKER:-MOSAIC_HELLO_OK}"
|
|||||||
|
|
||||||
load_config
|
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.
|
# 1. Build the image only if it is not already present.
|
||||||
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||||
echo "verify: image $IMAGE not found, building..." >&2
|
echo "verify: image $IMAGE not found, building..." >&2
|
||||||
|
|||||||
Reference in New Issue
Block a user