Files
stack/BUILD-LOG.md
T
jason.woltje 81f58b15c8 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
2026-09-02 18:32:50 -05:00

12 KiB
Raw Permalink Blame History

BUILD-LOG

Append-only build log for the Minimal Mosaic Stack container proof of concept. Each phase records the plan before it runs and the observed result after it runs. No credentials are recorded in this file.


Phase 1: Pi package discovery

Entry 1.1 — before

  • Timestamp: 2026-02-02 (session start, local)
  • Intended action: Locate the current package documentation for @earendil-works/pi-coding-agent, determine the current version, the supported noninteractive command, the custom system prompt mechanism, and the documented container authentication method.
  • Reason: The brief forbids guessing CLI flags, authentication paths, or SDK methods; all runtime commands must be derived from the package documentation.
  • Expected result: Documented answers for all five discovery questions, recorded below, with the Pi package version pinned in the project.

Entry 1.2 — after

  • Timestamp: 2026-02-02
  • Commands run:
    • Read README.md of the locally installed @earendil-works/pi-coding-agent package
    • Read docs/containerization.md, excerpts of docs/models.md and docs/providers.md
    • grep '"version"' .../pi-coding-agent/package.json
    • npm view @earendil-works/pi-coding-agent version
    • Inspected host auth store structure (keys only, values never printed)
  • Observed result:
    1. Package documentation: full docs ship inside the installed package (README.md plus a docs/ directory including docs/containerization.md).
    2. Current package version: 0.84.4 — the locally installed version and the npm registry latest are identical. Selected version to pin: 0.84.4.
    3. Supported noninteractive command: pi -p / pi --print — "Print response and exit". Documented in the CLI Reference. Print mode also merges piped stdin into the initial prompt (not used here).
    4. Custom system prompt: documented CLI flag --system-prompt <text> — "Replace default prompt (context files and skills still appended)". Because the generated contract prompt must fully control behavior, the run also passes the documented discovery-off switches (--no-context-files, --no-skills, --no-extensions, --no-prompt-templates, --no-themes) so nothing is appended from ambient files. (APPEND_SYSTEM.md / .pi/SYSTEM.md file conventions exist but the CLI flag is the documented noninteractive path.)
    5. Documented container authentication: docs/containerization.md documents the "Plain Docker" pattern: run the whole pi process in the container and pass provider API keys at runtime via environment variables (example: docker run -e ANTHROPIC_API_KEY ...). The documented alternative credential store is auth.json written by /login under the pi agent directory (~/.pi/agent/auth.json).
  • Decision: supply the credential at runtime as a read-only mounted credential file (host auth.json, which contains a static API-key entry for the built-in zai provider) mounted at the container pi agent directory, and additionally allow the documented environment-variable path (ZAI_API_KEY / ANTHROPIC_API_KEY) as an alternative. Provider and model are non-secret settings supplied via .env (PI_PROVIDER=zai, PI_MODEL=glm-5.3-flash).
  • Failure or correction: none. Host check confirmed no API-key environment variables are exported on the host, so the read-only auth.json mount is the working runtime credential path for this experiment.

Phase 2: Project scaffold

Entry 2.1 — before

  • Timestamp: 2026-02-02
  • Intended action: Create the contract fixtures (exact brief contents), the contract loader (src/load-contracts.sh), the one-shot agent runner (src/run-agent.sh), the four required scripts (scripts/build.sh, hello.sh, verify.sh, reset.sh), Containerfile, compose.yaml, pinned package.json + package-lock.json, .gitignore, README.md, LAYERS.md.
  • Reason: Implement exactly the file set the brief requires, with no extra machinery (no schemas, overlays, manifests, or policy loading).
  • Expected result: A complete project whose only remaining unknown is whether the pinned image builds and the real model request returns MOSAIC_HELLO_OK.

Entry 2.2 — after

  • Timestamp: 2026-02-02
  • Commands run: file creation; npm install --package-lock-only --ignore-scripts to generate the lockfile from the pinned dependency.
  • Observed result: All files created; package-lock.json pins @earendil-works/[email protected] (exact, no range).
  • Failure or correction: none.

Phase 3: Container image build

Entry 3.1 — before

  • Timestamp: 2026-02-02
  • Intended action: Run scripts/build.sh (Docker Compose build) to produce image mosaic-poc-agent:0.84.4 from node:24-bookworm-slim with the pinned Pi, the four contract fixtures at /opt/mosaic/contracts, and a non-root user (uid/gid 1000).
  • Reason: Phase 1 of the required proof path; node:24-bookworm-slim is the maintained base image used in Pi's own documented containerization example.
  • Expected result: docker compose build exits 0 and the image contains the contracts, the runner scripts, and the pinned pi binary, with no credentials baked in.

Entry 3.2 — after

  • Timestamp: 2026-02-02
  • Commands run: scripts/build.sh; docker run --rm mosaic-poc-agent:0.84.4 --version; id via --entrypoint; contract listing; credential file scan.
  • Observed result:
    • Build exit 0; image tagged mosaic-poc-agent:0.84.4.
    • pi --version inside the image reports 0.84.4 (and this run also executed the contract loader successfully, writing /var/lib/mosaic/system-prompt.md).
    • Container user is uid=1000(node) gid=1000(node) — non-root.
    • All four contract files present at /opt/mosaic/contracts with read-only permissions (0555).
    • Credential scan: no auth.json or other auth files exist in the image; /home/node/.pi/agent/ is empty in the image.
  • Failure or correction:
    1. First build failed: Docker Compose expects Dockerfile by default; fixed by setting build.dockerfile: Containerfile in compose.yaml.
    2. Second build failed: useradd exit 4 (uid 1000 already exists) because the maintained node image ships a node user at uid/gid 1000. Fixed by reusing the built-in node user (same 1000:1000 host mapping) instead of creating a duplicate mosaic user; container paths updated from /home/mosaic/... to /home/node/... in Containerfile, compose.yaml, README.md, .env.example.

Phase 4: Runtime verification

Entry 4.1 — before

  • Timestamp: 2026-02-02
  • Intended action: Run scripts/hello.sh (one-shot request: "Return your startup marker and nothing else."), then scripts/verify.sh (exact-match gate against MOSAIC_HELLO_OK), then the negative test (EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh must exit nonzero), then the scripts/reset.sh safety tests and a final rerun after reset.
  • Reason: Phases 27 of the required proof path plus acceptance criteria 511.
  • Expected result: hello prints only the marker; verify exits 0; negative test exits nonzero; reset refuses unsafe paths and succeeds on the real path; rerun after reset reproduces the success.

Entry 4.2 — after

  • Timestamp: 2026-02-02
  • Commands run: scripts/hello.sh; scripts/verify.sh; EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh; scripts/reset.sh (refusal tests: missing marker, symlink with canary file, then real reset, then missing dir); scripts/build.sh && scripts/verify.sh after reset; docker compose config mount inspection.
  • Observed result:
    • hello.sh: stdout exactly MOSAIC_HELLO_OK — a real model request (provider zai, model glm-5.3-flash, auth via the read-only mounted auth.json credential file). The request string contains no marker.
    • verify.sh: PASS: response matches expected marker, exit 0.
    • Negative test: FAIL: response does not match expected marker (expected MOSAIC_NOT_OK, actual MOSAIC_HELLO_OK), exit 1.
    • reset.sh refusal tests: missing marker → exit 1, nothing deleted; symlink (with canary file at the target) → exit 1, canary survived; real path with marker → removed, exit 0; missing dir → "nothing to remove", exit 0.
    • Rerun after reset: build + verify → PASS, exit 0 (criterion 11).
    • Resolved compose mounts: only /home/jwoltje/.mosaic-dev → /var/lib/mosaic (rw) and ~/.pi/agent/auth.json → /home/node/.pi/agent/auth.json (read-only). No ~/.mosaic or ~/.config/mosaic mounts, no Docker socket.
  • Failure or correction:
    1. First hello run: the contract loader's status line was printed on stdout, mixing runtime data into the model response stream and contaminating the exact-match capture. Fixed by sending the loader's status message to stderr (src/load-contracts.sh), rebuilt the image, reran: stdout is exactly the model response.
  • Credential check: no credential material appears in this log, in hello/verify output, or in the image (image scan found no auth files).

Result

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.