Two assertions in packages/mosaic/src/mutator-gate/runtime_tools_unittest.py pinned the behaviour D29 identifies as the defect: an envless revoke-lease.py returning rc=2. They are changed here under the test-is-the-defect exemption, stated on the record rather than made quietly. One new assertion is added to cover what the relaxation must NOT reach. Why the reclassification is justified. Not because the old pin was careless: it was deliberate entrypoint doctrine, born ine4d7d45(WI-3, #842) alongside the tool itself, and its twin pins the identical property for mutator-gate. Two measurements beat the doctrine. 1. Unsatisfiable precondition. The doctrine's only reachable firing point cannot be reached by the live fleet. Seats launch with zero MOSAIC_LEASE_* (measured, pane pid 8596; 19 of 19 live panes, see #1340), the framework ships no provisioning path for fleet launches, and the deployed extension does not enforce the total gate, since live seats use tools daily. 2. Gate-enforced redundancy. For the one case the doctrine protects, a leased session whose identity env is stripped: the strip must occur in the extension host's own env to reach the observer, at which point the gate also loses identity and denies GATE_UNAVAILABLE rc=2 on every tool call (measured). The stripped session may compact but cannot mutate. Pre-fix it merely died at compaction instead. No mutation path opens either way. The no-ungated-mutation property lives in the gate, not the revoker. That is why the revoker moves and the gate does not. Changes: 1. test_revoker_entrypoint_denies_when_identity_environment_is_absent becomes test_revoker_entrypoint_noops_when_identity_environment_is_absent and asserts rc=0. The stem is kept so history greps still find it. 2. NEW: test_revoker_entrypoint_denies_when_identity_environment_is_half_provisioned asserts rc=2 for socket-only and session-only environments. The no-op is reachable only on TOTAL absence; half-provisioned is a machinery-present failure and must still fail closed. main() already pinned this, but the entrypoint did not, and the entrypoint is what the runtime extension spawns. 3. In test_revoker_fails_closed_on_identity_reply_and_transport_errors, ONLY the ({}, ...) element leaves the cases list. The five machinery-present cases (malformed session id, ok:false, state:VERIFIED, OSError, JSONDecodeError) stay fail-closed and are untouched. 4. test_gate_entrypoint_denies_when_identity_environment_is_absent is left UNCHANGED, with a comment recording why. Relaxing it the same way would be a real downgrade. The revoker/gate asymmetry is intentional and must not be "fixed" later. Controls run, all three directions, none assumed: - Fix plus amendments: 26/26 OK, rc=0. - revoke-lease.py alone reverted to origin/next, amendments kept: FAILED (failures=1), rc=1. The amended entrypoint assertion still bites. - Guard weakened to `len(present) < 2` so half-provisioned falls through to the no-op: FAILED (failures=1), rc=1. The new assertion bites. Required by two independent re-verdicts, both REQUEST_CHANGES on head9fed3838, both of which superseded their own earlier APPROVE after CI surfaced the pinned contract: rev-security-01 review id 242, rev-security-02 review id 243. Each was asked to refute the reclassification before accepting it and each attempted it in writing. Item 2 above is rev-security-02's requirement and was not in the first re-verdict. Both re-affirm the guard's own semantics as correct; what was missing was the second half of a deliberate contract change. Refs #1338, #1340
@mosaicstack/mosaic
CLI package for the Mosaic self-hosted AI agent platform.
Usage
mosaic wizard # First-run setup wizard
mosaic gateway install # Install the gateway daemon
mosaic config show # View current configuration
mosaic config hooks list # Manage Claude hooks
Headless / CI Installation
Set MOSAIC_ASSUME_YES=1 (or ensure stdin is not a TTY) to skip all interactive prompts. The following environment variables control the install:
Gateway configuration (mosaic gateway install)
| Variable | Default | Required |
|---|---|---|
MOSAIC_STORAGE_TIER |
local |
No |
MOSAIC_GATEWAY_PORT |
14242 |
No |
MOSAIC_DATABASE_URL |
(none) | Yes if tier=team |
MOSAIC_VALKEY_URL |
(none) | Yes if tier=team |
MOSAIC_ANTHROPIC_API_KEY |
(none) | No |
MOSAIC_CORS_ORIGIN |
http://localhost:3000 |
No |
Admin user bootstrap
| Variable | Default | Required |
|---|---|---|
MOSAIC_ADMIN_NAME |
(none) | Yes (headless) |
MOSAIC_ADMIN_EMAIL |
(none) | Yes (headless) |
MOSAIC_ADMIN_PASSWORD |
(none) | Yes (headless) |
MOSAIC_ADMIN_PASSWORD must be at least 8 characters. In headless mode a missing or too-short password causes a non-zero exit.
Example: Docker / CI install
export MOSAIC_ASSUME_YES=1
export MOSAIC_ADMIN_NAME="Admin"
export MOSAIC_ADMIN_EMAIL="[email protected]"
export MOSAIC_ADMIN_PASSWORD="securepass123"
mosaic gateway install
Runtime launchers
mosaic claude # Launch Claude Code with Mosaic injection
mosaic yolo claude # …with --dangerously-skip-permissions
mosaic codex | opencode | pi
mosaic claudex (EXPERIMENTAL)
Runs GPT models inside the Claude Code harness by pointing Claude Code at a
local claude-code-proxy that
translates the Anthropic Messages API to a ChatGPT-subscription (Codex OAuth)
backend. This is not Anthropic Claude — model behavior, tool use, and output
quality may differ. Intended for evaluation, not production delivery.
mosaic claudex # launch (prompts through the proxy readiness gate)
mosaic yolo claudex # …with --dangerously-skip-permissions
mosaic claudex --print "hello" # trailing args are forwarded to Claude Code
Prerequisite: the claude-code-proxy binary must be installed and
authenticated (claude-code-proxy codex auth …). mosaic claudex runs a
preflight that verifies the binary, the OAuth state (triggering a device re-auth
if needed), and a trusted local listener before launching; it fails closed
if the proxy cannot be brought up with a verified identity.
Isolation (never touches your real Claude state). claudex always launches
against an isolated CLAUDE_CONFIG_DIR (default ~/.config/mosaic/claudex/home).
The ambient CLAUDE_CONFIG_DIR is deliberately ignored, and a guard proves the
resolved dir can never be — or live under — the real ~/.claude. A claudex
session therefore cannot mutate your normal Claude Code config.
No token leakage. claudex never reads the proxy's credential file. Claude
Code is handed only ANTHROPIC_AUTH_TOKEN=unused pointed at the loopback proxy;
the entire credential-bearing env family (ANTHROPIC_*, AWS_*, GOOGLE_CLOUD_*,
GOOGLE_APPLICATION_CREDENTIALS, *_TOKEN, *_KEY, *_SECRET, …) is stripped
from the composed environment. The Bedrock/Vertex routing switches
(CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, and the _SKIP_*_AUTH
pair) are force-removed regardless of value — otherwise their mere presence
would route Claude Code to the real Anthropic API via AWS/GCP and bypass the
proxy. The proxy holds the real OAuth credential.
Model tiers (override via env).
| Tier | Env var | Default |
|---|---|---|
| primary (opus/sonnet) | ANTHROPIC_MODEL |
gpt-5.6-sol |
| small/fast (haiku) | ANTHROPIC_SMALL_FAST_MODEL |
gpt-5.6-luna |
Operator-provided values win over the defaults. Additional overrides:
MOSAIC_CLAUDEX_CONFIG_DIR (isolated config dir), ANTHROPIC_BASE_URL (proxy
endpoint).
Hooks management
After running mosaic wizard, Claude hooks are installed in ~/.claude/hooks-config.json.
mosaic config hooks list # Show all hooks and enabled/disabled status
mosaic config hooks disable PostToolUse # Disable a hook (reversible)
mosaic config hooks enable PostToolUse # Re-enable a disabled hook
Set CLAUDE_HOME to override the default ~/.claude directory.