Commit Graph
150 Commits
Author SHA1 Message Date
Jason Woltje 77edb0dea2 feat(lease): add single-turn Claude promotion trigger 2026-08-11 20:51:03 -05:00
Jason Woltje 0e938b66ed fix(lease): ignore benign observer idle replies 2026-08-11 20:51:03 -05:00
Jason Woltje f0fef26eb7 fix(lease): constrain read-only tool carve-outs 2026-08-11 20:51:03 -05:00
Jason Woltje c9bccd4aae test(lease): assert pi carve-out capability 2026-08-11 20:51:03 -05:00
Jason Woltje 8ef2e5b91d test(lease): distinguish pi probe timeouts 2026-08-11 20:51:03 -05:00
Jason Woltje 4cab6c09fe test(lease): enforce read-only tool invariant 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Fable 5 d085182dc1 test: close W-0R review findings — assert the omission notice, skip chmod simulations under root
The independent W-0R review of 3592b92e passed but left two PLAUSIBLE
findings: the stderr notice for a legitimately-omitted operator source was
claimed and never asserted (a silent omission is the original defect in
miniature), and the chmod 0o000 unreadable simulations fail spuriously when
euid==0 (CAP_DAC_OVERRIDE). Falsifier for the new assertion: deleting the
notice block turns the suite red (failures=3); restoring returns green.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Opus 5 e949fa3767 fix(lease): refuse an incomplete law binding instead of silently shrinking it
build_construction skipped any normative source it could not read
(`except OSError: continue`) and promoted whatever remained. That is not a
degraded binding, it is a forged smaller one: the broker recomputes h_source /
h_payload from the fragments it is SENT (daemon.py:602-616), so an omitted
fragment is internally consistent and PAYLOAD_BINDING_MISMATCH cannot fire. A
partial law promotes exactly like a complete one and nothing downstream can tell
the difference.

Measured before this change, against a seeded home: with only USER.md readable,
the client produced a one-fragment construction with promotion=True. Removing
CONSTITUTION.md, STANDARDS.md or the runtime contract likewise promoted.

The classification mirrors the framework's own file ownership rather than
inventing one:

  * CONSTITUTION.md / AGENTS.md / STANDARDS.md are framework-owned and
    reconciled every upgrade (install.sh FRAMEWORK_OWNED,
    config/file-adapter.ts FRAMEWORK_OWNED_FILES), as is the per-runtime
    RUNTIME.md. Absent => IncompleteBinding. A deployment missing one is broken,
    not minimal.
  * SOUL.md / USER.md are deliberately not seeded by install.sh ("generated by
    `mosaic init`") and TOOLS.md is seeded on first install only, so their
    absence is legitimate. It is reported on stderr, never silent.

Unreadable is handled separately from absent for EVERY source, optional ones
included: a file that will not open is not a file that was never configured, and
collapsing the two is what let a permission change quietly shrink the law.

Also corrects this module's own docstring, which asserted that a VERIFIED lease
means "this agent is running THIS law". It does not. Both sides of the broker's
comparison originate in this client, so it detects corruption in transit and
nothing else. That overstatement is where the belief spread from; the stronger
claim needs the broker re-reading on-disk sources against a manifest the agent
cannot rewrite.

Test: promotion_binding_unittest.py, enumerated in test:framework-shell (the
enumeration guard's population is *test*.sh and does not cover Python, so an
unenumerated test here would simply never run). Falsifier executed: defeating the
guard while leaving the module API intact turns the suite red (12 failures);
restoring it returns green.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason Woltje e4dffb7c18 feat(launch): isolate harness homes and record immutable launch provenance
Mosaic wrote into the operator's harness base installs — ~/.claude,
~/.pi/agent, ~/.codex, ~/.config/opencode — for settings, instructions, and a
102-symlink skill farm per harness. Any experiment with hooks or gating
therefore mutated the operator's own tooling, and a broken framework change
could take out the very harness needed to repair it.

Harness home isolation
----------------------
Each runtime now reads config from a dedicated mosaic-owned home via the
harness's own config-dir variable:

  claude    CLAUDE_CONFIG_DIR     ~/.config/mosaic/.claude
  pi        PI_CODING_AGENT_DIR   ~/.config/mosaic/.pi     (replaces ~/.pi/agent)
  codex     CODEX_HOME            ~/.config/mosaic/.codex
  opencode  XDG_CONFIG_HOME       ~/.config/mosaic/.opencode

These paths are manifest-UNKNOWN, so rule 3 (#791) resolves them to operator
ownership and a keep-mode upgrade can neither overwrite nor prune them.
A bare `claude` / `pi` keeps its own config AND auth, making it a structural
break-glass rather than one depending on restoring a file under pressure.

opencode is blunter than the rest: it has no dedicated variable and follows XDG,
so isolation also relocates XDG lookups for anything it spawns. Documented in
place.

mosaic-sync-skills now links into those homes and cleans the legacy farms it
previously planted in base installs. Ownership is proven by RESOLUTION, not by
name — only symlinks resolving inside the canonical/local skills dirs are
removed, mirroring the refusal already in commands/skill.js. Verified against a
real install: codex's own .system directory survived while its 102 mosaic links
were removed. Both resolution prefixes are length-checked first; an empty prefix
would make "$resolved" == "$prefix/"* match every absolute path and delete
foreign symlinks.

Immutable launch record
-----------------------
Every launch now appends one record to fleet/run/sessions/events.ndjson before
exec. Mandatory, mechanical, no model involvement.

pi rewrites its own argv to a bare `pi`, so /proc/<pid>/cmdline destroys the
launch evidence — that has already produced a confident wrong diagnosis ("this
agent bypassed the launcher"), disproved only by the parent's argv and only
because the parent had not yet exited. A record written before exec is the only
place this survives.

The path is the #797 Runtime Session Ledger, already operator-classified and
already covered by test-upgrade-manifest-guard.sh, which seeds it and proves a
populated ledger survives keep-mode upgrades — but nothing shipped ever wrote
it. This implements it in the shape that guard already asserts (0600 files under
a 0700 dir).

`mosaic` writes session.launch; launch-runtime.py appends lease.register with
the broker session id and activation capability. They correlate by an explicit
MOSAIC_LAUNCH_ID, never by pid: execRuntime uses spawnSync, so the runtime is a
child with a different pid.

Records normative fragment digests (CONSTITUTION/AGENTS/SOUL/USER/STANDARDS/
TOOLS/RUNTIME) — the same set the broker hashes for promotion, so drift is
mechanically detectable rather than a matter of judgement.

Credential-safe: env is captured as PRESENT NAMES ONLY, and argv values over
256 bytes become a sha256 + length rather than being inlined.

Also fixes CLI_VERSION resolution: '@mosaicstack/mosaic/package.json' is not in
the package exports map and always throws ERR_PACKAGE_PATH_NOT_EXPORTED.
resolveTool() uses that same failing specifier, which is why its documented
preference for bundled tools over the deployed ~/.config/mosaic copy has never
once applied — noted in place, not fixed here.

Verified on sb-it-1-dt: isolated homes written and base installs byte-identical
for all four harnesses; 408 legacy symlinks removed with 1 foreign entry
preserved; launch records paired across the spawn boundary. typecheck shows zero
errors in launch.ts (the @mosaicstack/types failures are pre-existing and
reproduce on a pristine origin/main worktree).
2026-08-11 20:51:03 -05:00
Jason Woltje dc67590a96 fix(installer): propagate wizard gateway failures (#1120) 2026-08-09 05:01:09 -05:00
coder-mos2 906ad8dc30 wip(sync): merge main into next with combined resolutions 2026-08-02 23:11:22 -05:00
jason.woltjeandMos 2378665eaf feat(wake): W7 A10 idempotent installer + mosaic-wake.service (component-manifest, Gate-A, blank-reset retire, snapshot-guard, fail-closed install-validate) (#911)
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 04:19:09 +00:00
jason.woltjeandMos 48a0c86093 fix(lease-broker): recovery_runtime_unittest wait_ready() connect-probe (co-equal CI flake, cherry-pick #898) (#900)
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:26:38 +00:00
jason.woltjeandMos 79c8647fd9 fix(lease-broker): wait_ready() polls real connect-readiness not socket-file existence (flaky CI race) (#898)
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:10:15 +00:00
jason.woltjeandMos 529c177830 fix(update): mosaic update runs the install-ordering guard post-reseed (#882 --sync-only bypass) (#883)
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 22:18:34 +00:00
jason.woltjeandMos a32ce4c8f9 feat(869-c4): activation version-coupling assertion (Part of #869)
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @90eb48fa (fail-closed identity locks byte-unchanged verified), author id2 != approver id11, clean mosaic-coder author, CI green wp1992. #869 Point-1 CODE COMPLETE (C1/C3/C5/C2/C4).

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 19:07:27 +00:00
jason.woltjeandMos d351caad36 feat(869-c2): install-ordering enforcement-hook guard (Part of #869)
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @b6f36564 (8/8, verified vs real production settings template), author id2 != approver id11, clean mosaic-coder author, CI green wp1988.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 18:48:33 +00:00
jason.woltjeandMos 76b86a246e feat(869-c5): mosaic doctor activation-check (Part of #869)
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @e75e3238 (8/8), author id2 != approver id11, clean mosaic-coder author, CI green wp1987.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 18:38:21 +00:00
jason.woltjeandMos 2f50c0876b feat(869-c3): lease-broker supervisor unit (Part of #869)
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @75235ef8 (9/9, no live host mutation), author id2 != approver id11, CI green wp1971.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:19:48 +00:00
jason.woltjeandMos db90da347e feat(869-c1): activation-capability probe (Part of #869)
Part of #869

Mos (id-11) Gate-16 merge: independent 3-round APPROVE @c5a2bcc5, author id2 != approver id11, CI green wp1973.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:14:57 +00:00
jason.woltje b0d78d8632 fix(mosaic): de-flake mutator-class lease gate TTL-expiry test (#861) 2026-07-20 10:32:45 +00:00
jason.woltje 77c9a82614 fix(lease-broker): de-flake recovery_runtime b2 broker-socket ConnectionRefused race (#849) (#851) 2026-07-20 06:44:37 +00:00
jason.woltje 627cf2bb38 docs(fleet): add operator configuration guide (#789) 2026-07-20 05:22:25 +00:00
jason.woltje 0582a8912b WI-7 #834: T-C server-side branch-protection posture + R1 honesty amendment (#847) 2026-07-20 04:20:02 +00:00
jason.woltje 2509eb7646 WI-6 (#833): constrained recovery command + mosaic-context-refresh skill wrapper (#846) 2026-07-20 03:33:00 +00:00
jason.woltje 07553ead33 WI-5 #832: Receipt-challenge protocol (compaction-refresh, milestone 188) (#845) 2026-07-19 23:18:56 +00:00
jason.woltje e522b22fa4 WI-4 (#831): verbatim-hashed normative fragments (B_payload/H_payload) (#844) 2026-07-19 20:34:20 +00:00
jason.woltje e4d7d4502d WI-3 (#830): compaction observers → revoke + D4 same-PID generation auto-revoke (#842) 2026-07-19 19:46:28 +00:00
jason.woltje 8dfcf1903e fix(#838): bound broker reply deadlines + fail-close empty-read; de-flake acceptance harness (#839)
Bound broker reply deadlines (separate read/lock/send budgets, BROKER_BUSY-before-mutation, fresh post-handle send budget → closes drop-after-commit window); fail-close empty/truncated read → GATE_UNAVAILABLE deny. De-flakes the 1917 acceptance surface. terra CODE APPROVE (pi) + Opus SECREV APPROVED (claude) — RoR comment 18143. Promote-lease-lost-ACK residual = fail-safe two-generals observability-gap, routed to WI-3 D2-v5 as named-disclosed-bounded-residual (route i). Gate-16 3-principal author=gpt-sol.

closes #838
2026-07-18 07:15:50 +00:00
jason.woltje abd2791f59 feat(mosaic): WI-2 mutator-class guard for directive-freshness (#837)
WI-2: mutator-class guard for the compaction directive-freshness mechanism — command-position parser (prefix x var-indirection unified) + primitive-anchored invariant backstop + all-tools-hook fail-close. Parser-complete on principle: realistic evasion matrix RED-regression-covered, residual exotic evasions proven backstop-caught (B-tests), lens-convergence reached.

closes #829
2026-07-18 05:51:58 +00:00
jason.woltje 8ec67a1126 feat(mosaic): add authenticated external lease broker (#836) 2026-07-18 03:12:23 +00:00
jason.woltje d801d6c4c8 feat(mosaic): add secure skill registration CLI (#826) 2026-07-17 23:45:35 +00:00
jason.woltje d3bf52898b fix: reject unknown installer arguments (#825) 2026-07-17 22:16:51 +00:00
jason.woltje 3f77229e88 fix(#792): fleet roster ENOENT actionable exit + installer heading printf (#818) 2026-07-17 21:36:36 +00:00
jason.woltje 9ddc6fbda8 feat(fleet): mosaic fleet regen — regenerate roster-derived projections (PR3 of #791) (#813) 2026-07-17 03:17:55 +00:00
jason.woltje 31607a4af6 feat(mosaic): durable pre-update snapshot + verify net + restore CLI (#791 PR2) (#811) 2026-07-17 00:43:18 +00:00
jason.woltje 32a0ffba13 feat(mosaic): manifest-owned upgrade guard so updates never wipe operator config (#791) (#802) 2026-07-16 23:01:26 +00:00
jason.woltje 3be443c96d feat(mosaic): claudex isolated config + env-inject + yolo wiring (P2–P4 of #790) (#806) 2026-07-16 22:10:33 +00:00
jason.woltje 59f5f51ffd feat(mosaic): claudex proxy preflight + lifecycle (P1 of #790) (#793) 2026-07-16 21:15:33 +00:00
jason.woltje 9745bc3f29 feat(fleet): add reviewed v1-to-v2 migration preview (#788) 2026-07-16 13:11:16 +00:00
jason.woltje adad486b6f fix(fleet): enforce exact comms authority (#787) 2026-07-16 00:32:23 +00:00
jason.woltje c1aecfabe9 test(fleet): cover reconciler lifecycle gates (#786) 2026-07-15 16:46:13 +00:00
jason.woltje 499090508e feat(fleet): reconcile local roster state (#785) 2026-07-15 15:03:31 +00:00
jason.woltje bc5e73629e feat(fleet): add generation-guarded agent CRUD (#773) 2026-07-15 12:03:05 +00:00
jason.woltje 191efaefeb feat(fleet): enforce generated environment boundary (#772) 2026-07-15 08:40:32 +00:00
jason.woltje e9c4aa3e8b test(fleet): validate shipped artifact dispositions (#770) 2026-07-15 01:37:12 +00:00
jason.woltje a5e8e55401 feat(fleet): add shared role semantics (#768) 2026-07-15 00:53:47 +00:00
jason.woltje aa5b43bba2 feat(fleet): add roster v2 structural compiler (#764) 2026-07-14 20:37:52 +00:00
jason.woltje d077183554 docs(tess): remediate M5 qualification findings (#750) 2026-07-13 19:59:38 +00:00
jason.woltje 405984af5a De-hardcode orchestrator and interaction agent names (#748) 2026-07-13 18:59:27 +00:00