Files
stack/REPORT-T1.md
T

5.9 KiB
Raw Blame History

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:

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:

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:

> @mosaicstack/[email protected] 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:

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.