- 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
129 lines
5.0 KiB
Markdown
129 lines
5.0 KiB
Markdown
# Minimal Mosaic Stack container POC
|
|
|
|
Standalone experiment, not part of the Mosaic Stack repository or Software Factory.
|
|
|
|
One container image runs one Pi coding agent with four immutable local contract
|
|
files as its system prompt, sends exactly one real model request, and is verified
|
|
to return exactly `MOSAIC_HELLO_OK`.
|
|
|
|
## Layout
|
|
|
|
```text
|
|
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; 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 (credential-file path, env-var auth)
|
|
contracts/ CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md (immutable fixtures)
|
|
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 configured dataRoot)
|
|
/workspace agent workspace
|
|
```
|
|
|
|
## How it works
|
|
|
|
1. `scripts/build.sh` builds `mosaic-poc-agent:0.84.4` with Docker Compose.
|
|
2. On each run, `/opt/mosaic/src/load-contracts.sh` reads the four contract files
|
|
in fixed order (CONSTITUTION, STANDARDS, SOUL, USER), joins them with clear
|
|
separators, and writes `/var/lib/mosaic/system-prompt.md`.
|
|
3. `/opt/mosaic/src/run-agent.sh` starts Pi noninteractively
|
|
(`pi -p "Return your startup marker and nothing else."`) with
|
|
`--system-prompt "$(cat /var/lib/mosaic/system-prompt.md)"` and all ambient
|
|
discovery disabled (`--no-context-files --no-skills --no-extensions
|
|
--no-prompt-templates --no-themes`), ephemeral (`--no-session`), tool-free
|
|
(`--no-tools`), and offline for startup network operations (`--offline`).
|
|
4. `scripts/verify.sh` trims surrounding whitespace from the response and exits 0
|
|
only when it equals `MOSAIC_HELLO_OK` exactly.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh # must exit nonzero
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Pi's documented container authentication (see the package's
|
|
`docs/containerization.md`) is used, in this order:
|
|
|
|
1. **Read-only mounted credential file** (default): the host pi auth file
|
|
`~/.pi/agent/auth.json` is bind-mounted read-only to
|
|
`/home/node/.pi/agent/auth.json`. The host file holds a static API-key
|
|
entry for the built-in `zai` provider, so no token refresh writes are needed.
|
|
2. **Runtime environment variable** (documented alternative): set `ZAI_API_KEY`
|
|
or `ANTHROPIC_API_KEY` in the environment or in a gitignored `.env`; compose
|
|
passes them through. Pi's documented precedence applies.
|
|
|
|
Credentials are never committed, never copied into the image, and never printed.
|
|
`.env.example` contains non-secret settings only.
|
|
|
|
## Boundaries honored
|
|
|
|
- No mounts of `~/.mosaic` or `~/.config/mosaic`; no Docker socket mount.
|
|
- Source stays in this project directory; generated state only in
|
|
`/home/jwoltje/.mosaic-dev` (host) and `/var/lib/mosaic` (container).
|
|
- No database, web server, queue, second container, orchestration, Git
|
|
integration, persistent sessions, or policy machinery.
|