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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
```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):
|
||||
|
||||
+8
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user