# T1 report: canonical ungated Claude base and lease overlay ## Changed - Replaced `packages/mosaic/framework/runtime/claude/settings.json` with the canonical ungated base. It retains the model, QA hooks, plugins, command allowlist, permissions, and `mcpServers.sequential-thinking`. - Added `packages/mosaic/framework/runtime/claude/lease-overlay.json`. It contains only `hooks` and the six removed lease hook entries. - Added the byte-identical pre-split source fixture at `packages/mosaic/src/runtime/fixtures/claude-settings.gated.pre-split.json`. - Added `packages/mosaic/src/runtime/claude-settings-base.spec.ts`. `framework-manifest.txt` already declares `runtime/**`, so the new overlay is framework-owned and shipped without a manifest change. ## Lease-hook enumeration The actual template has six lease hook entries, matching fred's refined boundary: 1. `PreToolUse` matcher `.*`: `mutator-gate.py` 2. `Stop`: one combined command containing `receipt-observer-client.py` then `promote-complete.py` 3. `UserPromptSubmit` matcher `^/mosaic-promote$`: `promote-begin.py` 4. `PreCompact`: `revoke-lease.py --reason pre-compact` 5. `SessionStart` matcher `compact`: `revoke-lease.py --reason session-start-compact` 6. `SessionStart` matcher `resume|clear`: `revoke-lease.py --reason session-start-rollover --bump-generation` There is no delta from the refined six-entry enumeration. The Stop entry contains the receipt-observer and promote-complete commands together, rather than as two separate hook objects. ## Tests and checks `pnpm install --frozen-lockfile` was run first because `node_modules` was absent. It completed successfully. Red-first run before artifacts existed: ```text RUN v2.1.9 .../packages/mosaic ❯ src/runtime/claude-settings-base.spec.ts (4 tests | 4 failed) × keeps every lease command out of the ungated base → mutator-gate: expected true to be false × reconstructs the pre-split gated hooks while retaining the canonical MCP correction → ENOENT: .../lease-overlay.json × ships sequential-thinking in the base → expected undefined to deeply equal { 'sequential-thinking': ... } × limits the overlay to lease hook entries → ENOENT: .../lease-overlay.json ``` Final focused acceptance run: ```text RUN v2.1.9 .../packages/mosaic ✓ src/runtime/claude-settings-base.spec.ts (4 tests) 19ms Test Files 1 passed (1) Tests 4 passed (4) ``` `pnpm --filter @mosaicstack/mosaic lint` passed: ```text > @mosaicstack/mosaic@0.0.49 lint > eslint src ``` `pnpm --filter @mosaicstack/mosaic typecheck` failed on pre-existing workspace resolution and unrelated package errors. The new spec no longer appears in the error list. Initial failures include missing `@mosaicstack/{brain,forge,log,macp,memory,queue,storage,quality-rails,db,config,prdy,types}` declarations, followed by existing `fleet-backlog.ts`, `gateway-doctor.ts`, and TUI implicit-`any` errors. Exit status: 2. A focused legacy consumer run confirms an existing assumption that `settings.json` itself is gated: ```text pnpm --filter @mosaicstack/mosaic exec vitest run src/mutator-gate/mutator-gate.acceptance.spec.ts ❯ src/mutator-gate/mutator-gate.acceptance.spec.ts (20 tests | 6 failed) × non-dangerous parser residual is denied by the global all-tools hook without a lease → expected all-tools mutator-gate command in settings.json × Claude and Pi compaction observer wiring is complete and fail-closed → expected PreCompact/SessionStart revoke-lease hooks in settings.json ``` The other four failures in that focused run reported `STALE_GENERATION` where the test expected `MUTATOR_UNVERIFIED`, plus one successful-gate assertion. I did not redesign this legacy suite because the task explicitly says to report consumers that assume the base is gated. ## Consumers found Direct `runtime/claude/settings.json` path consumers found by the required repository grep: - `packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets`: copies the base to `~/.claude/settings.json`. - `packages/mosaic/src/commands/install-ordering-guard.ts` and `.spec.ts`: documentation and behavior assume the source embeds enforcement hooks. - `packages/mosaic/framework/tools/_scripts/test-install-ordering-guard.sh`: comments and assertions expect `mutator-gate.py` and `receipt-observer-client.py` in the base. - `packages/mosaic/src/mutator-gate/mutator-gate.acceptance.spec.ts`: reads the base and asserts mutator, promotion, and compaction lease wiring. - `packages/mosaic/src/lease-broker/promotion_trigger_unittest.py`: reads the base and asserts promotion wiring. - `packages/mosaic/src/lease-broker/recovery_runtime_unittest.py`: reads the base. - `packages/mosaic/src/runtime/update-checker.ts` and `.spec.ts`: references the path in settings wiring/update checks. - Documentation-only references: `docs/compaction-refresh/probes/p6_constrained_recovery.py`, `docs/plans/agent-reflection-loop-PRD.md`, `docs/tasks/544-agent-reflection-loop.md`, and the framework QA documentation/scripts found by grep. I did not change these consumers. The install/link and lease acceptance consumers must be taught to select and compose `lease-overlay.json` when a gated promotion seat is requested. That composition behavior is outside T1. ## Ambiguity handled The exact pre-split template fixture has no `mcpServers` key (SHA-256 `44e74ea1e9d424fffa020ee666402662ac856b88bf6ae7f3b8931eed29dc75a4`). The task simultaneously requires a byte-for-byte pre-split fixture, `mcpServers.sequential-thinking` in the base, and `deep-merge(base, overlay) == original`. Those three conditions cannot all hold because a merge cannot remove the required MCP key. The acceptance test preserves the exact fixture and asserts that the normalized merge equals the pre-split template plus the required canonical `mcpServers.sequential-thinking` correction. It verifies all original hook content is reconstructed and the base carries the required MCP. Production three-layer merge semantics remain W-F1 work.