# Contributing to the Mosaic Framework

The Mosaic framework is the open-source agent-operating layer that deploys to
`~/.config/mosaic/`. It is designed to be **forked and customized** — but the
shared core must stay operator-neutral, deduplicated, and upgrade-safe. This
guide is the contract for changing framework-owned files.

> Governance model and layer rationale: `constitution/LAYER-MODEL.md` (source-only).
> Requirements & phase history: `docs/design/framework-constitution/`.

---

## 1. The layer model (where does my change go?)

| Layer  | What                                                          | Owner            | On upgrade                              | File(s)                                      |
| ------ | ------------------------------------------------------------- | ---------------- | --------------------------------------- | -------------------------------------------- |
| **L0** | Constitution — the non-negotiable law (hard gates)            | Framework        | **Overwritten**                         | `CONSTITUTION.md`                            |
| **L1** | Standards & guides — how to do the work well                  | Framework        | Overwritten; user delta → `*.local.md`  | `STANDARDS.md`, `guides/*`                   |
| **L2** | Persona (SOUL) — agent name, tone, role                       | User (init)      | **Never overwritten**                   | `SOUL.md` (+ optional `SOUL.local.md`)       |
| **L3** | Operator (USER) — human identity, prefs, policy               | User (init)      | **Never overwritten**                   | `USER.md` (+ optional `USER.local.md`)       |
| **L4** | Project / runtime mechanism — per-repo deltas; harness wiring | Repo / framework | Project user-owned; runtime overwritten | `<repo>/AGENTS.md`, `runtime/<h>/RUNTIME.md` |

**The one sentence a user can rely on:** edit `SOUL.md` / `USER.md` and the
`.local.md` overlays — they survive every upgrade. To change framework behavior,
add a `.local.md` overlay; never edit a framework-owned file in place.

---

## 2. Operator hygiene (PII / secrets prohibition) — **blocking**

Framework-owned files ship publicly. They **must not** contain:

- Operator or personal identity (names, handles, pronouns, accessibility notes).
- Private `$HOME` paths, private hostnames, or domains.
- Secrets, tokens, or credentials (use `~/.config/mosaic/credentials.json`; the
  hook URL soft-degrades via `${OPENBRAIN_URL}`).

This is enforced by `tools/quality/scripts/verify-sanitized.sh`, wired **blocking**
in CI (`.woodpecker/ci.yml`). It runs two rule classes: structural (private-`$HOME`
defaults, dead paths, unrendered tokens) and a labeled current-contaminant denylist.
Run it locally before pushing:

```bash
bash packages/mosaic/framework/tools/quality/scripts/verify-sanitized.sh
```

Operator-specific behavior belongs in **your** `SOUL.md`/`USER.md`/`*.local.md`,
never in the shared core. (The "framework-PR firewall" in `CONSTITUTION.md` §4
states this as law for agents opening framework PRs.)

---

## 3. Dedup rule — one source, everyone references it

Hard gates live in **`CONSTITUTION.md` (L0) only**. `AGENTS.md`, `STANDARDS.md`,
and every `runtime/<h>/RUNTIME.md` **reference** the law — they never restate it.
Restating a gate is a defect: it creates two sources that drift. If you find a
gate duplicated outside L0, delete the copy and point to L0.

`AGENTS.md` is a thin dispatcher (load order + guide router + the tier-aware
self-load). Keep it that way; new procedure goes in `guides/*` (on-demand), not
in the resident core.

---

## 4. Resident line-count ceiling — **blocking**

The framework-owned files injected by value (`CONSTITUTION.md`, `AGENTS.md`, each
`runtime/<h>/RUNTIME.md`) are budgeted by **line count** — never by word count
(a word cap forces paraphrasing the law, the exact drift vector we removed).

```bash
bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
```

Wired blocking in CI. Gate **wording** stays intact; if a file legitimately needs
more lines, raise its ceiling in the script deliberately (in the same PR, with
rationale). The per-harness _total_ resident prompt (which also sums the user's
`SOUL.md`/`USER.md`) is a `mosaic doctor` runtime advisory — CI cannot see user
files, so it is out of CI scope by design (DESIGN §7).

---

## 5. Dual-installer parity rule

Two installers seed and migrate `~/.config/mosaic/`:

- **`framework/install.sh`** (bash) — the canonical installer.
- **`packages/mosaic/src/config/file-adapter.ts`** (TS) — the wizard path.

**Any change to seed lists, overwrite/preserve semantics, or migration MUST land
in BOTH**, validated by the **shared fixture suite**:

- `framework/tools/quality/scripts/test-install-migration.sh` (bash matrix)
- `packages/mosaic/src/config/file-adapter.test.ts` (vitest)

Both assert the same behavior: framework-owned files overwrite (backup-once to
`*.pre-constitution.bak`); user-seeded files seed-if-absent; `SOUL.md`/`USER.md`/
`*.local.md`/`credentials` are preserved. A change in one installer without the
other (and its fixtures) is incomplete.

---

## 6. Adding a harness adapter

A harness (runtime) is wired by:

1. `runtime/<h>/RUNTIME.md` — **mechanism only** (subagent syntax, hook/MCP wiring,
   injection method). No restated gates (see §3).
2. Launcher emission in `src/commands/launch.ts` — how the composed contract reaches
   the harness (system-prompt append vs. instructions file). Add the harness to the
   `RuntimeName` union and the runtime-path map.
3. `mosaic compose-contract <harness>` works automatically once the runtime path
   exists (it composes base + `*.local.md` overlays for that harness).

Then add a row to the compliance matrix (§8) and mark which gates are mechanical
vs. resident-only for the new harness.

---

## 7. Re-contamination rule

A green sanitization gate is not permanent. Before every PR:

- Do not reintroduce operator identity, private paths, or secrets (§2).
- Do not copy a gate out of L0 (§3).
- Do not add an unrendered template token or a dead path to a shipped file.

If `verify-sanitized.sh` goes red, that diff **is** your worklist — fix it, don't
suppress it.

---

## 8. Harness × gate compliance matrix

How each gate is enforced per harness. **Mechanical** = a hook/CI check the agent
cannot bypass. **Resident** = injected contract prose (strong, but not a hard stop).
**CI** = repo-side, harness-independent.

| Gate / mechanism                              | Claude      | Codex            | OpenCode         | Pi               |
| --------------------------------------------- | ----------- | ---------------- | ---------------- | ---------------- |
| Contract injection (resident-by-value)        | append SP   | instructions     | `AGENTS.md`      | append SP        |
| Operator overlays (`*.local`, composed)       | ✅          | ✅               | ✅               | ✅               |
| Bare-launch self-load (Tier-3, read L0)       | ✅          | ✅               | ✅               | ✅               |
| Sanitization (no PII) — `verify-sanitized`    | CI ✅       | CI ✅            | CI ✅            | CI ✅            |
| Resident budget ceiling                       | CI ✅       | CI ✅            | CI ✅            | CI ✅            |
| Migration parity (5-fixture, both installers) | CI ✅       | CI ✅            | CI ✅            | CI ✅            |
| `no-memory-write` (PreToolUse hook)           | **mech ✅** | resident-only ⚠️ | resident-only ⚠️ | resident-only ⚠️ |
| QA / typecheck (PostToolUse hooks)            | **mech ✅** | resident-only ⚠️ | resident-only ⚠️ | resident-only ⚠️ |
| Native heartbeat (fleet `ps` model/status)    | sidecar     | sidecar          | sidecar          | **native ✅**    |

⚠️ **Hook-parity gap (tracked, v2):** the mechanical PreToolUse/PostToolUse hooks
exist for Claude Code only. On Codex/OpenCode/Pi those gates are currently enforced
by the resident contract + CI, not by a per-tool hook. Closing hook parity is a
**v2** item, not part of this alpha.

---

## 9. Known limitations (accepted residual risks)

These are accepted with rationale (DESIGN §9); they are documented, not bugs:

- **Bare-launch overlays are base-only.** A harness started without `mosaic` never
  ran the composer, so `*.local.md` overlays are not applied. Mitigated by the
  unconditional Tier-3 self-load + the `mosaic doctor` nudge in `AGENTS.md`; not
  eliminated. Relaunch via `mosaic <harness>` to pick up overlays.
- **Bare-launch drift is undetected by `mosaic doctor`** (the launcher never ran).
- **Codex/OpenCode/Pi hook parity** is a tracked v2 gap (§8).
- **Live-launch cross-harness verification** is v2; the alpha verifies the composer
  by unit test (per-tier anchor + Tier-3 byte-equality), not a live launch.

**Deferred to v2 (explicit):** `constitution/` deploy directory; capability JSON
adapters; 3-way merge; `policy/*.md` composition; per-layer version stamps as a
migration driver.

---

## 10. PR checklist

- [ ] No operator identity / private paths / secrets (`verify-sanitized.sh` green).
- [ ] No gate restated outside `CONSTITUTION.md` (§3).
- [ ] Resident budget green (`check-resident-budget.sh`).
- [ ] Seed/migration changes landed in **both** installers + shared fixtures (§5).
- [ ] New harness → compliance-matrix row updated (§8).
- [ ] `prettier --check` + `pnpm lint` + `pnpm typecheck` + `pnpm test` green.
