# 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.
