feat(framework): P6 docs + compliance matrix + resident-budget CI (#606)
R9 + R10 of the Constitution alpha (in-repo deliverables): - framework/CONTRIBUTING.md: layer model, operator-hygiene/PII prohibition, dedup rule, resident budget, dual-installer parity rule, adding-a-harness, re-contamination rule, harness x gate compliance matrix (hook-parity gap marked as tracked-v2), known-limitations (DESIGN §9 residuals), PR checklist. - check-resident-budget.sh: line-count ceiling over framework-owned resident files (CONSTITUTION + AGENTS + each runtime/*/RUNTIME.md), with --self-test; replaces the crude inline ci.yml loop. Wired blocking in .woodpecker/ci.yml. Composer unit test (R9) already runs via pnpm test; verify-sanitized.sh (P1) already wired. Sanitization + budget + prettier all green. Remaining for the mission close: aiguide reconcile (separate repo) + the alpha tag (Lead cuts after full DoD §8 green + all phases merged; P5 #605 pending). Refs #606, #542 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
This commit is contained in:
185
packages/mosaic/framework/CONTRIBUTING.md
Normal file
185
packages/mosaic/framework/CONTRIBUTING.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# 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.
|
||||
93
packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
Executable file
93
packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-resident-budget.sh — resident line-count ceiling (R9 / DESIGN §7).
|
||||
#
|
||||
# Budgets the *container* (line count) of the framework-owned files that are
|
||||
# injected into every agent's context by value — the Constitution (L0), the
|
||||
# AGENTS dispatcher, and each runtime RUNTIME.md slice. Gate *wording* is never
|
||||
# capped (a word cap forces paraphrasing law — the exact drift vector P3 killed);
|
||||
# only the file's line count is bounded, so prose creep is caught in review.
|
||||
#
|
||||
# This is the CI-enforceable half of the budget. The per-harness *total* resident
|
||||
# prompt (which also includes user-generated SOUL.md/USER.md and the per-tier
|
||||
# slice) is summed by `mosaic doctor` as a runtime advisory — CI cannot see user
|
||||
# files, so it is deliberately out of scope here (DESIGN §7).
|
||||
#
|
||||
# Usage: check-resident-budget.sh [--self-test]
|
||||
# Exit: 0 = all within budget · 1 = a file exceeds its ceiling · 2 = self-test failed
|
||||
set -uo pipefail
|
||||
|
||||
FW="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" # packages/mosaic/framework
|
||||
|
||||
# Per-file ceilings (lines). Headroom above current counts; tighten as files settle.
|
||||
# Format: "<relative-path>:<max-lines>"
|
||||
CEILINGS=(
|
||||
"defaults/CONSTITUTION.md:120"
|
||||
"defaults/AGENTS.md:120"
|
||||
"runtime/claude/RUNTIME.md:90"
|
||||
"runtime/codex/RUNTIME.md:90"
|
||||
"runtime/opencode/RUNTIME.md:90"
|
||||
"runtime/pi/RUNTIME.md:90"
|
||||
)
|
||||
|
||||
# check_file <abs-path> <max> → echoes "<n>"; returns 0 if n<=max, 1 otherwise.
|
||||
check_file() {
|
||||
local path="$1" max="$2" n
|
||||
n=$(wc -l <"$path" 2>/dev/null || echo 0)
|
||||
n=$((n + 0))
|
||||
echo "$n"
|
||||
[ "$n" -le "$max" ]
|
||||
}
|
||||
|
||||
run_budget() {
|
||||
local fail=0 rel max abs n
|
||||
printf '%-32s %8s %8s %s\n' "FILE" "LINES" "CEILING" "STATUS"
|
||||
for entry in "${CEILINGS[@]}"; do
|
||||
rel="${entry%%:*}"
|
||||
max="${entry##*:}"
|
||||
abs="$FW/$rel"
|
||||
if [ ! -f "$abs" ]; then
|
||||
printf '%-32s %8s %8s %s\n' "$rel" "-" "$max" "MISSING"
|
||||
fail=1
|
||||
continue
|
||||
fi
|
||||
n=$(check_file "$abs" "$max")
|
||||
if [ "$n" -le "$max" ]; then
|
||||
printf '%-32s %8s %8s %s\n' "$rel" "$n" "$max" "ok"
|
||||
else
|
||||
printf '%-32s %8s %8s %s\n' "$rel" "$n" "$max" "OVER BUDGET"
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
return "$fail"
|
||||
}
|
||||
|
||||
self_test() {
|
||||
local tmp rc
|
||||
tmp=$(mktemp)
|
||||
# 3 lines, ceiling 5 → within budget (rc 0)
|
||||
printf 'a\nb\nc\n' >"$tmp"
|
||||
check_file "$tmp" 5 >/dev/null
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ]; then echo "self-test FAIL: under-budget file flagged"; rm -f "$tmp"; return 2; fi
|
||||
# 6 lines, ceiling 5 → over budget (rc 1)
|
||||
printf 'a\nb\nc\nd\ne\nf\n' >"$tmp"
|
||||
check_file "$tmp" 5 >/dev/null
|
||||
rc=$?
|
||||
if [ "$rc" -ne 1 ]; then echo "self-test FAIL: over-budget file not flagged"; rm -f "$tmp"; return 2; fi
|
||||
rm -f "$tmp"
|
||||
echo "self-test OK"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--self-test" ]; then
|
||||
self_test
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if run_budget; then
|
||||
echo "Resident budget: all framework-owned resident files within ceiling."
|
||||
exit 0
|
||||
else
|
||||
echo "Resident budget EXCEEDED — trim prose or raise the ceiling deliberately (see DESIGN §7)." >&2
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user