- Containerized Pi hello-world proof (image mosaic-poc-agent:0.84.4, non-root) - Four immutable contract fixtures loaded into a generated system prompt - build/hello/verify/reset scripts with exact-match gating and reset safety - Documented Pi discovery (v0.84.4, -p mode, --system-prompt, container auth) - Append-only BUILD-LOG with corrections; deferred layers in LAYERS.md - Architecture plan: docs/plans/2026-09-02_atomic-mosaic-foundation.md
110 lines
8.9 KiB
Markdown
110 lines
8.9 KiB
Markdown
# 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 2–7 of the required proof path plus acceptance criteria 5–11.
|
||
- 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.
|
||
|
||
|