Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a2bcc516 | ||
|
|
ac44a1aea7 | ||
|
|
c64a04e7dd | ||
|
|
763cecc381 |
@@ -4,14 +4,6 @@ pnpm-lock.yaml
|
||||
**/node_modules
|
||||
**/drizzle
|
||||
**/.next
|
||||
# Python build/test artifacts — same category as node_modules/dist/.next above.
|
||||
# Prettier must never scan generated trees; without these a local venv poisons
|
||||
# `pnpm format:check` with thousands of third-party files.
|
||||
**/venv
|
||||
**/__pycache__
|
||||
**/.mypy_cache
|
||||
**/.pytest_cache
|
||||
**/htmlcov
|
||||
.claude/
|
||||
docs/tess/TASKS.md
|
||||
docs/scratchpads/
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# mos-remediation — LIVE BOARD (keep < 8 KB)
|
||||
|
||||
**Phase:** PLANNING — adversarial task decomposition IN FLIGHT.
|
||||
**Updated:** 2026-07-31 (mos-remediation orchestrator; seat active on `mosaic-fleet`).
|
||||
|
||||
## Head
|
||||
|
||||
- Mission charter + 15 decisions + 4-build plan: PERSISTED (`docs/remediation/MISSION.md`).
|
||||
- HOLD lifted for this workstream (Jason 2026-07-31). Nothing implemented yet — planning first.
|
||||
- Orchestrator seat `mos-remediation` is LIVE and owns the mission. Residency attestation: PASS.
|
||||
- TASK-0 (env + push mission package) IN PROGRESS — checkout deps repaired, gates being verified honestly.
|
||||
- TASK-1 (adversarial decomp) DISPATCHED for real, both planners on GUARANTEED-CLEAN context.
|
||||
|
||||
## In-flight
|
||||
|
||||
| Task | Owner | State |
|
||||
| ---------------------------------------------------- | --------------- | --------------------------------------------- |
|
||||
| TASK-0 env repair + push `remediation/mission-setup` | mos-remediation | IN PROGRESS — deps installed; gates verifying |
|
||||
| Decomp (robustness side) → `DECOMP-OPUS.md` | planner-opus | WORKING (clean session) |
|
||||
| Decomp (pragmatic side) → `DECOMP-SOL.md` | planner-sol | WORKING (reset to 0.0% ctx before dispatch) |
|
||||
| Reconcile both decomps → `docs/remediation/TASKS.md` | mos-remediation | BLOCKED on the two decomps |
|
||||
|
||||
## Fleet seats
|
||||
|
||||
- mos-remediation — project orchestrator (Claude, /src/mosaic-stack, socket `mosaic-fleet`) — ACTIVE
|
||||
- planner-opus — adversarial planner (robustness), Opus 5, socket `default` — WORKING
|
||||
- planner-sol — adversarial planner (pragmatic), gpt-5.6-sol, socket `default` — WORKING
|
||||
- rev-974 — mosaicstack reviewer identity (id 16, write:repository) — idle, on call
|
||||
- Mos (mos-claude) — lead coordinator, socket `default` — relay path to Jason
|
||||
|
||||
## Gate status
|
||||
|
||||
- Delivery gates active: author≠reviewer, diff-blind pre-registered checks, CI-green, merged-PR completion.
|
||||
- Freeze: LIFTED for this workstream only.
|
||||
- Git identity: `MOSAIC_GIT_IDENTITY=mos-dt-0` INTERIM. Mos ruled gate-16 HOLDS (author≠reviewer is what
|
||||
gate-16 protects; rev-974 reviews, mos-dt-0 never self-reviews). Dedicated identity TRACKED, Mos provisions.
|
||||
- Remote control: native `/remote-control` NOT wired in this runtime. Path is **Mos-relay**
|
||||
(Jason ↔ mos-claude via Discord ↔ mos-remediation via agent-send). Not a blocker.
|
||||
|
||||
## Sequencing (from MISSION.md)
|
||||
|
||||
1. Spine + choke-point service (MACP wiring @ mosaic_orchestrator.py::run_single_task) + PG/Redis
|
||||
2. Rotation daemon (finish Mission Control Plane, reuse packages/coord)
|
||||
3. Comms service (envelope→service→PG/Redis→adapters)
|
||||
4. Hygiene + conformance harness
|
||||
Cross-cutting retirements: flat-file tracking, 3 MACP islands, silent MOSAIC BYPASS.
|
||||
|
||||
## Dogfood evidence captured this session (live failure classes, not hypotheticals)
|
||||
|
||||
- **D-1 / P-ACTIVATION + hygiene — committed `.npmrc` hard-pins `store-dir=/root/.local/share/pnpm/store`.**
|
||||
Correct for the CI container (runs as root), fatal for EVERY non-root local checkout: `EACCES` on
|
||||
`/root/.local/share/pnpm/store/v10/server/server.json`. A committed config that only works on one
|
||||
runtime is exactly the activation-skew class. Fix candidate: make store-dir env-overridable, not hardcoded.
|
||||
- **D-2 / hygiene — husky `prepare` fails `EPERM` copying into root-owned `.husky/_/`.** Repo working
|
||||
tree has root-owned dirs (`.husky/`, repo root) under a non-root agent. Worked around with the
|
||||
intended `HUSKY=0` escape hatch (does NOT disable the existing pre-commit/pre-push hooks).
|
||||
- **D-3 / P-FLEET-001 — the seats running this mission are UNMANAGED.** `mos-remediation`, `rev-974`,
|
||||
`planner-opus`, `planner-sol` appear in NO roster (`~/.config/mosaic/fleet/roster.yaml`, `agents/`).
|
||||
Planners run on socket `default`; the roster declares `mosaic-fleet`. This is the exact
|
||||
"one roster-owned socket/host + quarantine unmanaged + stale GC" failure P-FLEET-001 indicts —
|
||||
observed on the remediation mission's own fleet. Prerequisite for INBOX identity-addressing.
|
||||
- **D-4 / P-LIFECYCLE + hygiene — a dispatched agent silently IGNORED an in-message context reset.**
|
||||
planner-sol was at 64.3%/372k; the brief asked it to reset first; it began work on dirty context anyway.
|
||||
Only an out-of-band `/new` driven by the orchestrator guaranteed clean state. Confirms the postmortem
|
||||
thesis: **instructions are not enforcement.** Reset must be a mechanical pre-dispatch step, not a request.
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-07-31 — Mission set up by Mos post-postmortem (15/15 decided). Dogfood posture active.
|
||||
- 2026-07-31 — Mos: stale `.mosaic/orchestrator/mission.json` is RESIDUE of the disabled Python
|
||||
orchestrator rail that this plan RETIRES. Do NOT invest in it; do NOT build on that rail. The 0/0
|
||||
milestone banner is cosmetic. (Supersedes any plan to repair it.)
|
||||
- 2026-07-31 — Mos: planners must be dispatched with GUARANTEED clean context, not requested-clean.
|
||||
Prior default-socket planner sessions predate this mission; dirty context is the indicted hygiene.
|
||||
- 2026-07-31 — mos-remediation: worker briefs forbid all git ops and restrict each worker to a single
|
||||
named output file, so two planners can share one checkout without a branch race (M2-era incident doctrine).
|
||||
@@ -1,44 +0,0 @@
|
||||
# mos-remediation — Orchestrator Kickstart / Compaction-Survival Resume
|
||||
|
||||
**You are `mos-remediation`, the project orchestrator for the Mosaic Stack remediation, launched in `/src/mosaic-stack`.**
|
||||
This file is your fail-closed resume procedure. Read it on EVERY fresh/cleared session and on the FIRST turn
|
||||
after any compaction. This mission's whole point is that manual compaction-survival is fragile — so follow this
|
||||
mechanically until Build 3 (rotation) makes it automatic.
|
||||
|
||||
## On resume (do in order, before any orchestration action)
|
||||
|
||||
1. `cd /src/mosaic-stack` and confirm you are on the remediation working branch.
|
||||
2. Read `docs/remediation/MISSION.md` — the charter (goal, 4 builds, 15 decisions, sequencing, directives).
|
||||
3. Read `docs/remediation/BOARD.md` — the LIVE state: current phase, in-flight tasks, fleet seat assignments,
|
||||
gate status. This is your single source of in-flight truth (kept small).
|
||||
4. Read the discussion checkpoint for full rationale if needed:
|
||||
`../jarvis-brain/docs/scratchpads/postmortem/REMEDIATION-DISCUSSION-STATE.md` (or the jarvis-brain repo path).
|
||||
5. **Residency attestation (fail-closed):** restate from the reloaded files — (a) the goal in one line, (b) the
|
||||
current build/phase, (c) the BOARD head (in-flight tasks + who owns them). If you cannot, HALT and re-read.
|
||||
Do NOT act on memory alone; a compaction may have dropped context silently.
|
||||
|
||||
## Standing invariants (never violate)
|
||||
|
||||
- **North star:** deterministic-right-answer → code/gate; LLM only for judgment.
|
||||
- **Delivery gates:** author≠reviewer; PRE-REGISTERED diff-blind checks committed before reading the diff;
|
||||
CI terminal-green; completion = merged PR + closed issue. rev-974 = the mosaicstack reviewer identity.
|
||||
- **Dogfooding:** every fix validated against its live seed case (MISSION.md lists them).
|
||||
- **Tracking → DB** (hard cutover); do NOT re-invest in flat-file tracking. jarvis-brain PDA is off-limits.
|
||||
- **Git identity:** export `MOSAIC_GIT_IDENTITY=<your-seat>` so wrappers author correctly and survive respawn.
|
||||
|
||||
## After every significant event
|
||||
|
||||
Overwrite stale lines in `BOARD.md`, keep it < 8 KB, commit + push. The board IS your checkpoint until the
|
||||
DB-backed rotation daemon (Build 3) exists. Persist typed state (phase, tasks, owners, gates) — never the transcript.
|
||||
|
||||
## Fleet
|
||||
|
||||
- Adversarial planners: `planner-opus` (robustness), `planner-sol` (pragmatic) — dispatch for task decomposition; reconcile their oppositional decomps.
|
||||
- Coders/reviewers: dispatch per roster + delivery gates. Comms: `~/.config/mosaic/tools/tmux/agent-send.sh`
|
||||
(`-L <socket> -s <dst> -S <yourhost>:<yourseat> --class <class>`); always pass `-S`.
|
||||
- Lead coordinator: Mos (`mos-claude`). Escalate only on the Constitution's escalation triggers.
|
||||
|
||||
## Remote control
|
||||
|
||||
On first startup, activate remote control for this session (`/remote-control`) so Jason can reach/drive you while
|
||||
away. If the command is unavailable in this runtime, report it to Mos and continue — it is not a blocker.
|
||||
@@ -1,98 +0,0 @@
|
||||
# MACP wiring investigation
|
||||
|
||||
**Scope:** `/src/mosaic-stack` inspected at HEAD `b79336a8c11e2a4646a47ff8d295a226e0c71404`; read-only. Existing dirty/untracked state was not touched.
|
||||
|
||||
## Verdict
|
||||
|
||||
**(c) STRANDED.** `packages/macp` is exported, unit-tested, and registered as a CLI command group, but no production dispatch/execution code invokes its credential resolver, gate runner, or event emitter.
|
||||
A separate MACP-named OpenClaw/orchestrator rail exists, but it redefines task/result types and gate/event logic instead of importing `@mosaicstack/macp`; direct `mosaic yolo|claude|codex|opencode|pi` also bypasses it.
|
||||
|
||||
## 1. Production call sites vs tests
|
||||
|
||||
### Production references to `@mosaicstack/macp`
|
||||
|
||||
| Surface | Evidence | Actual use |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Unified CLI | `packages/mosaic/src/cli.ts:8,385` | Imports and registers `registerMacpCommand`; no task/gate/event execution. |
|
||||
| Forge | `packages/forge/src/types.ts:1,17,68,79` | **Type-only** imports of `GateEntry` and `TaskResult`. Pipeline calls an injected abstract executor at `packages/forge/src/pipeline-runner.ts:189-190,299-300`, not MACP. |
|
||||
| Mosaic package metadata | `packages/mosaic/package.json:36`; `packages/mosaic/src/runtime/update-checker.ts:172` | Dependency/update inventory only. |
|
||||
| Agent | No match under production `packages/agent/src/**` | No MACP import/call. |
|
||||
| Coord | No match under production `packages/coord/src/**`; dependency list is only `@mosaicstack/types` at `packages/coord/package.json:25-27` | No MACP import/call. |
|
||||
| Plugins | No `@mosaicstack/macp` import under `plugins/**` | No package use; the MACP-named plugin is an independent implementation (below). |
|
||||
|
||||
**Repository-wide production call-site search result:** excluding `packages/macp/**`, tests, worktrees, and build output, there are **zero** calls to `runGate`, `runGates`, `emitEvent`, `appendEvent`, or `resolveCredentials`.
|
||||
|
||||
### `packages/macp` implementation is internally connected only
|
||||
|
||||
- Public exports: `packages/macp/src/index.ts:1-48` exports Task/GateEntry/MACPEvent/TaskResult, credential resolution, `runGate(s)`, risk-floor, and event emission.
|
||||
- Gate runner calls its own event emitter: `packages/macp/src/gate-runner.ts:187-236`.
|
||||
- Event persistence implementation appends NDJSON to a caller-supplied path: `packages/macp/src/event-emitter.ts:11-27`.
|
||||
- There is **no exported programmatic `submit` implementation** in `packages/macp/src/index.ts:1-48`; only the CLI placeholder named `submit`.
|
||||
|
||||
### Test-only invocations
|
||||
|
||||
- Gate runner: `packages/macp/__tests__/gate-runner.test.ts:96-242` invokes `runGate/runGates`.
|
||||
- Event ledger: `packages/macp/__tests__/event-emitter.test.ts:46-133` invokes `appendEvent/emitEvent` against temporary `events.ndjson` files.
|
||||
- Credential resolver: `packages/macp/__tests__/credential-resolver.test.ts` exercises resolver behavior.
|
||||
- CLI tests only verify command registration: `packages/macp/src/cli.spec.ts:37-73`; `packages/mosaic/src/cli-smoke.spec.ts:8` imports registration.
|
||||
|
||||
## 2. Gate on the live dispatch path
|
||||
|
||||
### Direct Mosaic runtime launch bypasses MACP
|
||||
|
||||
- Runtime commands dispatch directly to harness launch: `packages/mosaic/src/commands/launch.ts:730-801`.
|
||||
- Claude/Pi go through the lease broker, then spawn the runtime: `packages/mosaic/src/commands/launch.ts:817-843`.
|
||||
- Commander wiring sends `mosaic yolo <runtime>` and direct runtime commands to `launchRuntime`: `packages/mosaic/src/commands/launch.ts:1102-1157,1165-1167`.
|
||||
- None of those ranges imports/calls `@mosaicstack/macp`, `runGates`, or `emitEvent`.
|
||||
|
||||
**Result:** a direct `mosaic yolo`, `mosaic claude/codex/opencode/pi`, or underlying exec does not create a typed MACP Task, run the package gate-runner, or append a package MACPEvent.
|
||||
|
||||
### Coord bypasses MACP
|
||||
|
||||
- Coord reads/updates `docs/TASKS.md`: `packages/coord/src/runner.ts:6,306-386`; parser/writer is `packages/coord/src/tasks-file.ts:326-377`.
|
||||
- Coord launches a child process directly: `packages/coord/src/runner.ts:397-427`.
|
||||
- Mission state is its own `.mosaic/orchestrator/mission.json`/`next-task.json`: `packages/coord/src/mission.ts:8-12`; `packages/coord/src/runner.ts:15-16,355-384`.
|
||||
|
||||
**Result:** Coord task execution has no MACP Task validation, package gate runner, or event append.
|
||||
|
||||
### Forge bypasses MACP execution
|
||||
|
||||
- Forge defines its own `ForgeTask` and abstract `TaskExecutor`: `packages/forge/src/types.ts:48-80`.
|
||||
- The production CLI injects a **stub executor** that immediately reports completion with empty gates: `packages/forge/src/cli.ts:13-31,167,185`.
|
||||
|
||||
**Result:** even `mosaic forge run` does not execute MACP gates or persist MACP events.
|
||||
|
||||
### Separate MACP-named rail is not `packages/macp`
|
||||
|
||||
- OpenClaw plugin registers an ACP backend named `macp`: `plugins/macp/src/index.ts:1-18,72-102`.
|
||||
- It locally redefines `OrchestratorTask`, `TaskResult`, and gate-result shapes instead of importing package types: `plugins/macp/src/macp-runtime.ts:43-77`.
|
||||
- It appends directly to `.mosaic/orchestrator/tasks.json`, triggers an external controller, and polls `results/<task>.json`: `plugins/macp/src/macp-runtime.ts:290-329,437-483`.
|
||||
- The controller independently implements `append_event`, `emit_event`, shell execution, gate execution, and results: `packages/mosaic/framework/tools/orchestrator-matrix/controller/mosaic_orchestrator.py:29-91,126-276`.
|
||||
- Its gate loop runs raw string gates after worker success: `mosaic_orchestrator.py:213-235`; it does not support the package's structured `GateEntry`/AI-review behavior.
|
||||
- Current checkout disables this controller: `.mosaic/orchestrator/config.json:2` (`"enabled": false`).
|
||||
- Plugin references `tools/macp/dispatcher/pi_runner.ts` at `plugins/macp/src/macp-runtime.ts:85-91`, but `tools/macp/` does not exist in this checkout.
|
||||
|
||||
**Result:** there is a parallel, optionally enabled MACP-shaped rail, not package integration. It cannot make `packages/macp` the enforced path.
|
||||
|
||||
## 3. Event ledger status
|
||||
|
||||
- Package persistence exists only as a library primitive: `packages/macp/src/event-emitter.ts:11-27` appends JSON lines to an arbitrary `eventsPath`.
|
||||
- Package event emission is reached only from package `runGates`: `packages/macp/src/gate-runner.ts:204-236`.
|
||||
- No production caller invokes package `runGates/emitEvent/appendEvent`; therefore no runtime destination path is configured for the package ledger.
|
||||
- Test-only ledgers use temp paths: `packages/macp/__tests__/event-emitter.test.ts:35-133`; gate tests use temp `events.ndjson`: `packages/macp/__tests__/gate-runner.test.ts:171-242`.
|
||||
- The separate Python controller writes `.mosaic/orchestrator/events.ndjson`: `mosaic_orchestrator.py:129-133,159-161,219-235`; the Mosaic Framework plugin only **reads** that file for context at `plugins/mosaic-framework/src/index.ts:279-316,430-438`.
|
||||
- In this checkout, `.mosaic/orchestrator/events.ndjson` is absent and the controller is disabled (`.mosaic/orchestrator/config.json:2`).
|
||||
|
||||
**Conclusion:** `MACPEvent` from `packages/macp` is defined/tested but not emitted or persisted by live production call sites. The similarly shaped Python ledger is a duplicate island.
|
||||
|
||||
## 4. Coord link
|
||||
|
||||
- `packages/coord` has no `@mosaicstack/macp` dependency/import: `packages/coord/package.json:25-27`; no matches in `packages/coord/src/**`.
|
||||
- Coord's task model is Markdown `docs/TASKS.md` plus mission/session JSON: `packages/coord/src/tasks-file.ts:1-10,257-377`; `packages/coord/src/mission.ts:8-12`; `packages/coord/src/runner.ts:306-427`.
|
||||
- It does not consume `.mosaic/orchestrator/events.ndjson`, MACP Task, MACPEvent, GateEntry, or TaskResult.
|
||||
|
||||
**Conclusion:** Coord and `packages/macp` are disconnected islands.
|
||||
|
||||
## Shortest wiring gap
|
||||
|
||||
**Single integration point:** replace the duplicated execution/gate/event block in `mosaic_orchestrator.py::run_single_task` (`:126-276`) with one production Node `TaskExecutor` backed by `@mosaicstack/macp` (typed Task validation + `resolveCredentials` + `runGates` + `emitEvent`), and make Coord/Forge/OpenClaw submit through that executor. This queue/controller choke point is where `yolo|acp|exec` worker outcomes can be gated and journaled before completion is recorded.
|
||||
@@ -1,79 +0,0 @@
|
||||
# Mosaic Stack Remediation — Mission Charter
|
||||
|
||||
**Owner:** project orchestrator `mos-remediation` (Claude, launched in `/src/mosaic-stack`).
|
||||
**Origin:** 2026-07-16..31 fleet lifecycle postmortem. **Status:** PLANNING (task decomposition).
|
||||
**HOLD lifted** for this workstream by Jason, 2026-07-31 — "begin full mosaic fleet operation on this."
|
||||
|
||||
## Goal
|
||||
|
||||
Convert the 15 accepted postmortem remediation proposals into a working, **dogfooded** implementation.
|
||||
**North star:** anything with a deterministic right answer moves OUT of the LLM into a deterministic
|
||||
gate/program; the LLM handles only genuine judgment.
|
||||
|
||||
## Decision record (authoritative, immutable)
|
||||
|
||||
- **15/15 proposals decided: 13 accept, 2 modify (P-AUTHORITY-001, P-INBOX-001), 0 reject.**
|
||||
- Site + `annotations.json`: `jarvis-brain/docs/postmortem-spec/site/` (committed, origin/main).
|
||||
- Discussion checkpoint (rich rationale per proposal): `jarvis-brain/docs/scratchpads/postmortem/REMEDIATION-DISCUSSION-STATE.md`.
|
||||
- Postmortem report: mosaicstack/stack PR #107 (merged 88f4ee04).
|
||||
- MACP wiring scout (verdict c=STRANDED): `/tmp/macp-wiring-investigation.md` (copy into this dir — see TODO).
|
||||
|
||||
## The plan — 15 proposals collapse to 4 builds + hygiene
|
||||
|
||||
| Build | Absorbs | What it is |
|
||||
| ------------------------------------------------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **1. One choke-point service** (mechanical enforcer) | MISSION, STATE, AUDIT, WRAPPER, QUEUE | Deterministic program every task/data mutation flows through. **Wire the stranded `@mosaicstack/macp`** in at `mosaic_orchestrator.py::run_single_task` — typed tasks, gate-runner, event ledger, credential binding, tri-state write outcomes. |
|
||||
| **2. One durable spine + hot path** | (storage under everything) | **PG system-of-record + Redis hot queue** (transactional-outbox). Mission/tasks/state-claims/audit-ledger/comms-inbox all land here. |
|
||||
| **3. Rotation lifecycle** (finish the Mission Control Plane) | LIFECYCLE, CONTRACT, GUIDE, RECOVERY | Coordinator daemon: contract-hash binding, compaction-detected → rotate-not-compact, checkpoint→fresh-session→rehydrate, broker-independent recovery. Deterministic, not an LLM. Reuse `packages/coord`; existing PRD at `docs/mission-control/`. |
|
||||
| **4. Comms service** | AUTHORITY, INBOX (+ versioning roadmap) | Envelope (comms/v1) → sole-path service → PG/Redis → pluggable adapters (tmux→Matrix/Discord/Slack/Telegram). Version the protocol, not participants. |
|
||||
| **+ Hygiene & proof** | FLEET, WORKFLOW, CONFORMANCE | One roster-owned socket/host + stale GC; allowlist auto-sync; the conformance harness that fault-injects the failure classes and proves builds 1–4 hold. |
|
||||
|
||||
## The finding that sets the cost
|
||||
|
||||
**Built-but-unwired disease.** `@mosaicstack/macp` is stranded (nothing calls it); `packages/coord` primitives
|
||||
exist; the Mission Control PRD exists; PG + Redis already run in-stack. Three duplicate MACP islands, an
|
||||
orphaned context loader, a fail-open bypass. **Work = wire + consolidate + retire, NOT greenfield. "Finish, don't re-spec."**
|
||||
|
||||
## Sequencing (skeleton — adversarial decomposition refines this)
|
||||
|
||||
1. **Spine + choke-point service** (builds 1+2) — foundation; unlocks MISSION/STATE/AUDIT/WRAPPER/QUEUE at one integration point.
|
||||
2. **Rotation daemon** (build 3) on that spine — the drift fix proper.
|
||||
3. **Comms service** (build 4) — envelope → service → PG/Redis → adapters; retire direct-tmux.
|
||||
4. **Hygiene + conformance** (build 5) — fleet convergence, allowlist sync, dogfood harness.
|
||||
|
||||
- **Cross-cutting retirements:** flat-file orchestration tracking (hard cutover to DB), the 3 duplicate MACP islands, the silent `MOSAIC BYPASS`.
|
||||
|
||||
## Standing directives (Jason, 2026-07-31)
|
||||
|
||||
- **Dogfooding:** validate EACH fix against the live fleet failure that motivated it. Seed acceptance tests:
|
||||
Pi brick (RECOVERY), scout-bounce (INBOX/FLEET), gate-6 inert + #1019 recursion (QUEUE), identity drift
|
||||
(WRAPPER), auto-sync sweep (WORKFLOW), #1018 stale-consumed (INBOX). The fleet is its own test bed.
|
||||
- **Orchestration tracking → DB**, hard cutover ("rip off the bandaid"), NO flat-file interim. jarvis-brain
|
||||
PDA flat-files untouched. Current flat-file tracking runs as-is/unhardened until DB tracking is real, then one clean replace.
|
||||
|
||||
## The 15 decisions (one-line; full rationale in the checkpoint)
|
||||
|
||||
1. **P-ACTIVATION-001** accept — transactional CLI+hooks+broker+version release; block launch on skew, fail-SAFE.
|
||||
2. **P-AUTHORITY-001** MODIFY — structured authenticated inbox; envelope carries comms-PROTOCOL version; version the protocol not participants; N-version window.
|
||||
3. **P-LIFECYCLE-001** accept — rotation not recursive compaction; pre-empt at token threshold; enforcer = deterministic coordinator; = finish Mission Control Plane.
|
||||
4. **P-MISSION-001** accept — bind lanes to mission+task ledger; convention exists, ENFORCEMENT is the gap; mission+tasks → DB spine (hard cutover).
|
||||
5. **P-QUEUE-001** accept — repair queue transport + exit-asserting non-null-case tests (gate-6 was INERT fleet-wide; #1019 fix recursed the same bug).
|
||||
6. **P-STATE-001** accept — typed claims (source/confidence/TTL) not prose blob; MACP typed record; integrity fail-closed HMAC; don't fork a 4th island.
|
||||
7. **P-AUDIT-001** accept — MACPEvent lifecycle ledger; EXTEND enum to lifecycle events; runtime-neutral (executor-emitted); retire duplicate Python ledger.
|
||||
8. **P-WRAPPER-001** accept — identity derives from seat name + survives respawn; tri-state write outcomes MANDATORY; name safe target metadata.
|
||||
9. **P-CONTRACT-001** accept — bind session to contract hash; re-anchor on policy-change OR compaction-detected; stale generation loses authority MECHANICALLY.
|
||||
10. **P-INBOX-001** MODIFY — sole-path comms SERVICE; PG durable SoR + Redis hot queue (outbox, reconciliation sweeper); pluggable adapters; protocol-first, PG-first-then-Redis.
|
||||
11. **P-RECOVERY-001** accept — broker-independent bootstrap recovery; honest capability labeling; break-glass LOUD+AUDITED+TEMPORARY not silent permanent bypass.
|
||||
12. **P-GUIDE-001** accept — delete `/compact and continue` from orchestrator path (keep for ephemeral); removal = substitution (wire rotation trigger).
|
||||
13. **P-FLEET-001** accept — one roster-owned socket/host; quarantine unmanaged; stale-session GC; prerequisite for INBOX identity-addressing.
|
||||
14. **P-WORKFLOW-001** accept — auto-sync ALLOWLIST not denylist; worktree/lease isolation for agent docs/source; DB-tracking obviates the flat-file-sweep criterion.
|
||||
15. **P-CONFORMANCE-001** accept — fleet lifecycle harness on REAL runtime artifacts + fault injection; the 100-rotations-lossless bar is a test; target the DB substrate.
|
||||
|
||||
## Fleet operating model
|
||||
|
||||
- **Project orchestrator** `mos-remediation` (this seat) owns the mission; coordinates under Mos (lead).
|
||||
- **Adversarial task decomposition:** `planner-opus` (robustness) + `planner-sol` (pragmatic) each decompose
|
||||
the plan independently; orchestrator reconciles into `TASKS.md`/DB tasks. Oppositional by design.
|
||||
- **Delivery gates (non-negotiable):** author≠reviewer, PRE-REGISTERED diff-blind acceptance checks committed
|
||||
before reading the diff, CI terminal-green, completion = merged PR + closed issue. rev-974 = mosaicstack reviewer.
|
||||
- **Compaction survival:** see `KICKSTART.md` in this dir — the resume procedure. Persist typed state, not transcript.
|
||||
@@ -21,6 +21,7 @@ import { registerRestoreCommand } from './commands/restore.js';
|
||||
import { registerSkillCommand } from './commands/skill.js';
|
||||
// prdy is registered via launch.ts
|
||||
import { registerLaunchCommands } from './commands/launch.js';
|
||||
import { registerLeaseCapabilityProbe } from './commands/lease-activation-probe.js';
|
||||
import { registerAuthCommand } from './commands/auth.js';
|
||||
import { registerFederationCommand } from './commands/federation.js';
|
||||
import { registerGatewayCommand } from './commands/gateway.js';
|
||||
@@ -78,6 +79,10 @@ Command Groups:
|
||||
|
||||
registerLaunchCommands(program);
|
||||
|
||||
// ─── lease activation capability probe (hidden; #869 Point-1 C1) ────────
|
||||
|
||||
registerLeaseCapabilityProbe(program);
|
||||
|
||||
// ─── login ──────────────────────────────────────────────────────────────
|
||||
|
||||
program
|
||||
|
||||
@@ -806,7 +806,14 @@ function launchRuntime(runtime: RuntimeName, args: string[], yolo: boolean): nev
|
||||
process.exit(0); // Unreachable but satisfies never
|
||||
}
|
||||
|
||||
function defaultLeaseBrokerSocket(env: NodeJS.ProcessEnv = process.env): string {
|
||||
/**
|
||||
* Resolve the lease broker's control socket path. Exported (in addition to
|
||||
* being used internally by execLeaseGatedRuntime) so the C1 activation probe
|
||||
* (lease-activation-probe.ts) can perform the same resolution when checking
|
||||
* whether the broker supervisor is reachable — detection only, this never
|
||||
* connects to the socket itself.
|
||||
*/
|
||||
export function defaultLeaseBrokerSocket(env: NodeJS.ProcessEnv = process.env): string {
|
||||
if (env['MOSAIC_LEASE_BROKER_SOCKET']) return env['MOSAIC_LEASE_BROKER_SOCKET'];
|
||||
const runtimeDir = env['XDG_RUNTIME_DIR'];
|
||||
if (runtimeDir) return join(runtimeDir, 'mosaic-lease', 'broker.sock');
|
||||
@@ -895,7 +902,12 @@ function delegateToScript(scriptPath: string, args: string[], env?: Record<strin
|
||||
* bundled in the @mosaicstack/mosaic npm package (always matches the installed
|
||||
* CLI version) over the deployed copy in ~/.config/mosaic/ (may be stale).
|
||||
*/
|
||||
function resolveTool(...segments: string[]): string {
|
||||
/**
|
||||
* Exported so the C1 activation probe (lease-activation-probe.ts) can resolve
|
||||
* the same lease-broker launcher/daemon artifacts execLeaseGatedRuntime()
|
||||
* uses, for detection-only supervisor presence checks.
|
||||
*/
|
||||
export function resolveTool(...segments: string[]): string {
|
||||
try {
|
||||
const req = createRequire(import.meta.url);
|
||||
const mosaicPkg = dirname(req.resolve('@mosaicstack/mosaic/package.json'));
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Command } from 'commander';
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
LEASE_ACTIVATION_CAPABILITY,
|
||||
LEASE_CAPABILITY_PROBE_COMMAND,
|
||||
defaultCapabilityProbe,
|
||||
defaultResolveCliEntry,
|
||||
defaultSupervisorProbe,
|
||||
leaseEnforcementActivatable,
|
||||
registerLeaseCapabilityProbe,
|
||||
type LeaseActivationCapability,
|
||||
type SupervisorProbeResult,
|
||||
} from './lease-activation-probe.js';
|
||||
|
||||
/**
|
||||
* Red-first tests for issue #869 Point-1 C1 — leaseEnforcementActivatable().
|
||||
*
|
||||
* Root cause under test: #828 shipped the lease broker's ENFORCEMENT half
|
||||
* (hooks) and ACTIVATION half (execLeaseGatedRuntime + a running daemon.py
|
||||
* broker) on different channels, and they drifted — the published CLI
|
||||
* tarball lacked the activation half even though it existed in source. The
|
||||
* predicate here must say NO when either half of activation is unavailable,
|
||||
* and only YES when both are genuinely present — never based on "does the
|
||||
* source file exist", but on a real capability signal + real supervisor
|
||||
* detection.
|
||||
*/
|
||||
|
||||
const compatibleCapability: LeaseActivationCapability = { ...LEASE_ACTIVATION_CAPABILITY };
|
||||
const presentSupervisor: SupervisorProbeResult = {
|
||||
supervisorPresent: true,
|
||||
socketPath: '/run/user/1000/mosaic-lease/broker.sock',
|
||||
};
|
||||
|
||||
describe('leaseEnforcementActivatable', () => {
|
||||
it('is false when the activation capability is absent (null)', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => null,
|
||||
probeSupervisor: () => presentSupervisor,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when the activation capability name does not match', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => ({
|
||||
name: 'some-other-capability',
|
||||
version: LEASE_ACTIVATION_CAPABILITY.version,
|
||||
}),
|
||||
probeSupervisor: () => presentSupervisor,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when the activation capability version is incompatible (stale/newer build)', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => ({
|
||||
name: LEASE_ACTIVATION_CAPABILITY.name,
|
||||
version: LEASE_ACTIVATION_CAPABILITY.version + 1,
|
||||
}),
|
||||
probeSupervisor: () => presentSupervisor,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when the supervisor artifacts (launcher/daemon) are not present', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => compatibleCapability,
|
||||
probeSupervisor: () => ({
|
||||
supervisorPresent: false,
|
||||
socketPath: presentSupervisor.socketPath,
|
||||
}),
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when the supervisor socket path is not resolvable', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => compatibleCapability,
|
||||
probeSupervisor: () => ({ supervisorPresent: true, socketPath: null }),
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when BOTH capability and supervisor are absent', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => null,
|
||||
probeSupervisor: () => ({ supervisorPresent: false, socketPath: null }),
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('is true when a compatible capability AND a resolvable supervisor are both present', () => {
|
||||
const result = leaseEnforcementActivatable({
|
||||
getCapability: () => compatibleCapability,
|
||||
probeSupervisor: () => presentSupervisor,
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('uses the real default probes when no deps are injected (does not throw)', () => {
|
||||
// No live broker / built CLI is guaranteed in a test environment, so this
|
||||
// only asserts the predicate degrades to a safe boolean rather than
|
||||
// throwing — the fail-closed behavior itself is covered by the injected
|
||||
// cases above.
|
||||
expect(() => leaseEnforcementActivatable()).not.toThrow();
|
||||
expect(typeof leaseEnforcementActivatable()).toBe('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultCapabilityProbe', () => {
|
||||
it('returns null (fail-closed) when no built CLI artifact is resolvable', () => {
|
||||
// Deterministic regardless of ambient host state (e.g. a host that has
|
||||
// already run `pnpm build`, which would otherwise make this pass or fail
|
||||
// depending on whether dist/cli.js happens to exist) — inject a resolver
|
||||
// pointing at a path that cannot exist, rather than relying on this
|
||||
// checkout being unbuilt. The probe must report "no capability" rather
|
||||
// than fabricate one from source-tree presence — this is the exact
|
||||
// distinction #828's version skew needed: source existing is not the
|
||||
// same as the published artifact advertising the capability.
|
||||
const result = defaultCapabilityProbe({
|
||||
resolveCliEntry: () => '/nonexistent/mosaic-lease-activation-probe-test/cli.js',
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
describe('positive path — injected resolver, isolated scratch dir (never the real dist/)', () => {
|
||||
// A prior version of this test staged the stub cli.js at the package's
|
||||
// REAL resolved dist/ path and relied on afterEach to clean up "only
|
||||
// what it created" — which meant a host with a real pre-built
|
||||
// dist/cli.js (ordinary `pnpm build && pnpm test`) would have its real
|
||||
// ~26KB compiled CLI silently overwritten by an 87-byte stub, with no
|
||||
// restoration of the original content. That is exactly the kind of
|
||||
// build-artifact corruption #869 exists to prevent. This version uses
|
||||
// dependency injection exclusively: defaultCapabilityProbe() is never
|
||||
// called with its default resolver here, so it can never touch the real
|
||||
// package dist/ at all — proven below by asserting that path's
|
||||
// existence is unchanged by the test.
|
||||
it('returns the real {name, version} capability from a stub cli.js in a temp dir, and leaves the real dist/ untouched', () => {
|
||||
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
const realDistDir = join(packageRoot, 'dist');
|
||||
const realDistPreexisted = existsSync(realDistDir);
|
||||
|
||||
const scratchDir = mkdtempSync(join(tmpdir(), 'mosaic-lease-capability-probe-'));
|
||||
try {
|
||||
const scratchCliPath = join(scratchDir, 'cli.js');
|
||||
// Minimal stand-in for the built CLI's hidden __lease-capability
|
||||
// subcommand — prints exactly what registerLeaseCapabilityProbe()
|
||||
// wires the real `mosaic __lease-capability` command to print.
|
||||
writeFileSync(
|
||||
scratchCliPath,
|
||||
`process.stdout.write(JSON.stringify(${JSON.stringify(LEASE_ACTIVATION_CAPABILITY)}));\n`,
|
||||
);
|
||||
|
||||
const result = defaultCapabilityProbe({ resolveCliEntry: () => scratchCliPath });
|
||||
expect(result).toEqual(LEASE_ACTIVATION_CAPABILITY);
|
||||
|
||||
// The real package dist/ must be byte-for-byte untouched: this test
|
||||
// never invokes the default resolver, so the path's mere existence
|
||||
// (created or not) must be unchanged by having run this test.
|
||||
expect(existsSync(realDistDir)).toBe(realDistPreexisted);
|
||||
} finally {
|
||||
rmSync(scratchDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultResolveCliEntry', () => {
|
||||
it('resolves the bare "@mosaicstack/mosaic" specifier (the exported "." entry), never the non-exported "./package.json" subpath', () => {
|
||||
// Fully isolated from the real filesystem/package state (no dependency
|
||||
// on whether @mosaicstack/mosaic has been built on this host) via an
|
||||
// injected fake resolver that mirrors Node's real behavior: the "."
|
||||
// export resolves fine, but "./package.json" is NOT in package.json's
|
||||
// `exports` map, so real `require.resolve` throws
|
||||
// ERR_PACKAGE_PATH_NOT_EXPORTED for it. This is genuinely red-first
|
||||
// against the reviewer-found bug: the old implementation resolved the
|
||||
// "./package.json" subpath here, which this fake throws on — the new
|
||||
// implementation must resolve only the bare specifier.
|
||||
const requestedSpecifiers: string[] = [];
|
||||
const fakeResolve = (specifier: string): string => {
|
||||
requestedSpecifiers.push(specifier);
|
||||
if (specifier === '@mosaicstack/mosaic') return '/fake/pkg/dist/index.js';
|
||||
throw new Error(`ERR_PACKAGE_PATH_NOT_EXPORTED: ${specifier}`);
|
||||
};
|
||||
|
||||
const result = defaultResolveCliEntry(fakeResolve);
|
||||
|
||||
expect(result).toBe(join('/fake/pkg/dist', 'cli.js'));
|
||||
expect(requestedSpecifiers).toEqual(['@mosaicstack/mosaic']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultSupervisorProbe', () => {
|
||||
it('returns a well-shaped result without starting or connecting to anything', () => {
|
||||
const result = defaultSupervisorProbe({});
|
||||
expect(typeof result.supervisorPresent).toBe('boolean');
|
||||
expect(result.socketPath === null || typeof result.socketPath === 'string').toBe(true);
|
||||
});
|
||||
|
||||
it('resolves a socket path from an explicit MOSAIC_LEASE_BROKER_SOCKET override', () => {
|
||||
const result = defaultSupervisorProbe({ MOSAIC_LEASE_BROKER_SOCKET: '/tmp/explicit.sock' });
|
||||
expect(result.socketPath).toBe('/tmp/explicit.sock');
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerLeaseCapabilityProbe', () => {
|
||||
it('registers a hidden subcommand named __lease-capability', () => {
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerLeaseCapabilityProbe(program);
|
||||
|
||||
const registered = program.commands.find((c) => c.name() === LEASE_CAPABILITY_PROBE_COMMAND);
|
||||
expect(registered).toBeDefined();
|
||||
// Commander exposes "hidden" only as help-output suppression (no public
|
||||
// getter) — assert the observable behavior instead of a private field.
|
||||
expect(program.helpInformation()).not.toContain(LEASE_CAPABILITY_PROBE_COMMAND);
|
||||
});
|
||||
|
||||
it('prints the capability constant as JSON when invoked', () => {
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerLeaseCapabilityProbe(program);
|
||||
|
||||
let written = '';
|
||||
const originalWrite = process.stdout.write.bind(process.stdout);
|
||||
process.stdout.write = ((chunk: string) => {
|
||||
written += chunk;
|
||||
return true;
|
||||
}) as typeof process.stdout.write;
|
||||
|
||||
try {
|
||||
program.parse(['node', 'mosaic', LEASE_CAPABILITY_PROBE_COMMAND]);
|
||||
} finally {
|
||||
process.stdout.write = originalWrite;
|
||||
}
|
||||
|
||||
expect(JSON.parse(written)).toEqual(LEASE_ACTIVATION_CAPABILITY);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Lease-enforcement activation probe (issue #869, Point-1 card C1).
|
||||
*
|
||||
* Root cause this exists to guard against (#828 version skew): the
|
||||
* ENFORCEMENT half of the lease broker (PreToolUse/Stop hooks —
|
||||
* `mutator-gate.py`, `receipt-observer-client.py` — wired via the framework
|
||||
* reseed) and the ACTIVATION half (`execLeaseGatedRuntime()` in `launch.ts`,
|
||||
* which chains the runtime through `launch-runtime.py`, injects
|
||||
* `MOSAIC_LEASE_*`, and requires a running `daemon.py` broker) ship on
|
||||
* different channels. When the published CLI tarball lags behind an
|
||||
* enforcement reseed, the gate correctly fails CLOSED on absent identity —
|
||||
* but every tool call then denies with GATE_UNAVAILABLE. That fail-closed
|
||||
* behavior is intentional and must not change (see the C-REGRESS note in
|
||||
* `runtime_tools_unittest.py`); this module exists so a downstream
|
||||
* install-ordering guard (C2, out of scope here) can refuse to WIRE
|
||||
* enforcement in the first place on a host that cannot ACTIVATE it.
|
||||
*
|
||||
* `leaseEnforcementActivatable()` answers one narrow question: "if
|
||||
* enforcement were wired right now, could activation actually satisfy it?"
|
||||
* It is a real capability probe — not a "does the source file exist" check
|
||||
* — and both of its inputs are injectable so tests can drive every branch
|
||||
* without a live broker or an installed CLI on PATH.
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'node:path';
|
||||
import type { Command } from 'commander';
|
||||
import { defaultLeaseBrokerSocket, resolveTool } from './launch.js';
|
||||
|
||||
// ─── Capability signal (owned by the activation half) ──────────────────────
|
||||
|
||||
/**
|
||||
* Versioned identity for the activation contract `execLeaseGatedRuntime()`
|
||||
* implements. OWNED by the activation half of the lease broker. Bump
|
||||
* `version` only when the activation contract itself changes (env vars
|
||||
* injected, chaining behavior, socket protocol, etc.) — deliberately
|
||||
* independent of the package's npm semver, because #828 happened precisely
|
||||
* because the npm version was NOT bumped even though the shipped artifact
|
||||
* fell out of sync. A build that cannot advertise this exact
|
||||
* `{ name, version }` pair does not implement the contract a caller is
|
||||
* relying on, whatever its package.json claims.
|
||||
*/
|
||||
export interface LeaseActivationCapability {
|
||||
readonly name: string;
|
||||
readonly version: number;
|
||||
}
|
||||
|
||||
export const LEASE_ACTIVATION_CAPABILITY: LeaseActivationCapability = {
|
||||
name: 'lease-runtime-activation',
|
||||
version: 1,
|
||||
};
|
||||
|
||||
/** Hidden CLI probe subcommand name — wired via {@link registerLeaseCapabilityProbe}. */
|
||||
export const LEASE_CAPABILITY_PROBE_COMMAND = '__lease-capability';
|
||||
|
||||
function capabilityMatches(candidate: LeaseActivationCapability | null): boolean {
|
||||
return (
|
||||
candidate !== null &&
|
||||
candidate.name === LEASE_ACTIVATION_CAPABILITY.name &&
|
||||
candidate.version === LEASE_ACTIVATION_CAPABILITY.version
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the hidden `__lease-capability` probe subcommand. Prints the
|
||||
* capability this BUILD advertises as compact JSON to stdout and exits 0.
|
||||
* Deliberately undocumented (hidden from `--help`): it is an internal signal
|
||||
* for {@link defaultCapabilityProbe}, not a user-facing command.
|
||||
*/
|
||||
export function registerLeaseCapabilityProbe(program: Command): void {
|
||||
program
|
||||
.command(LEASE_CAPABILITY_PROBE_COMMAND, { hidden: true })
|
||||
.description('Internal: print the lease-activation capability this build advertises')
|
||||
.action(() => {
|
||||
process.stdout.write(JSON.stringify(LEASE_ACTIVATION_CAPABILITY));
|
||||
});
|
||||
}
|
||||
|
||||
/** Injectable Node module resolver — matches `require.resolve`'s signature
|
||||
* narrowly (specifier in, absolute path out, or throws). Defaults to the
|
||||
* real `createRequire(import.meta.url).resolve`. Injectable so tests can
|
||||
* exercise WHICH specifier {@link defaultResolveCliEntry} resolves (the
|
||||
* reviewer-found bug was resolving the wrong one) without depending on
|
||||
* whether `@mosaicstack/mosaic` has actually been built on the test host —
|
||||
* and without ever touching the real package's `dist/` to find out. */
|
||||
export type ModuleResolver = (specifier: string) => string;
|
||||
|
||||
/**
|
||||
* Resolve the CLI's built entrypoint (`dist/cli.js`). Resolves via the
|
||||
* package's "." export (already present in package.json's `exports` map)
|
||||
* rather than a "./package.json" subpath — the latter is NOT exported, so
|
||||
* `require.resolve('@mosaicstack/mosaic/package.json')` throws
|
||||
* ERR_PACKAGE_PATH_NOT_EXPORTED on every real install. The "." export
|
||||
* resolves to `dist/index.js`; `cli.js` is its sibling in the same built
|
||||
* `dist/` directory (see package.json's `bin.mosaic`).
|
||||
*
|
||||
* Exported standalone (and injectable via {@link CapabilityProbeDeps}) so
|
||||
* tests can exercise this resolution logic in isolation, or point
|
||||
* {@link defaultCapabilityProbe} at a scratch directory instead of ever
|
||||
* touching the real installed package's `dist/` — a test corrupting a real
|
||||
* build artifact is exactly the artifact-integrity failure class this card
|
||||
* exists to prevent (#828).
|
||||
*/
|
||||
export function defaultResolveCliEntry(
|
||||
resolve: ModuleResolver = createRequire(import.meta.url).resolve,
|
||||
): string {
|
||||
const mainEntry = resolve('@mosaicstack/mosaic');
|
||||
return join(dirname(mainEntry), 'cli.js');
|
||||
}
|
||||
|
||||
/** Injectable inputs for {@link defaultCapabilityProbe}. */
|
||||
export interface CapabilityProbeDeps {
|
||||
/** Resolve the CLI entrypoint (`cli.js`) to probe. Defaults to
|
||||
* {@link defaultResolveCliEntry}. Inject to point at an isolated scratch
|
||||
* location in tests — never at the real package's `dist/`. */
|
||||
resolveCliEntry?: () => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Real capability lookup. Resolves the installed `@mosaicstack/mosaic`
|
||||
* package's BUILT entrypoint (`dist/cli.js` — the published artifact a user
|
||||
* actually runs, not this TypeScript source file) and executes its hidden
|
||||
* `__lease-capability` probe subcommand out-of-process. A build that lacks
|
||||
* the subcommand, fails to execute, or reports an incompatible
|
||||
* `{ name, version }` is treated as having NO activation capability.
|
||||
*
|
||||
* This is the check that would have caught #828's version skew: the
|
||||
* source-tree activation half existed, but the published tarball's `dist/`
|
||||
* did not carry it, so this probe — reading the actually-resolvable built
|
||||
* artifact rather than trusting source-tree presence — would report null.
|
||||
*/
|
||||
export function defaultCapabilityProbe(
|
||||
deps: CapabilityProbeDeps = {},
|
||||
): LeaseActivationCapability | null {
|
||||
try {
|
||||
const resolveCliEntry = deps.resolveCliEntry ?? defaultResolveCliEntry;
|
||||
const cliEntry = resolveCliEntry();
|
||||
if (!existsSync(cliEntry)) return null;
|
||||
|
||||
const output = execFileSync(process.execPath, [cliEntry, LEASE_CAPABILITY_PROBE_COMMAND], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 2000,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
|
||||
const parsed: unknown = JSON.parse(output);
|
||||
if (
|
||||
typeof parsed !== 'object' ||
|
||||
parsed === null ||
|
||||
typeof (parsed as Record<string, unknown>)['name'] !== 'string' ||
|
||||
typeof (parsed as Record<string, unknown>)['version'] !== 'number'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const candidate = parsed as { name: string; version: number };
|
||||
return { name: candidate.name, version: candidate.version };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Supervisor / socket resolution (detection only) ───────────────────────
|
||||
|
||||
/** Detection-only supervisor/socket probe result. Never starts the broker
|
||||
* and never connects to the socket — presence and path resolution only. */
|
||||
export interface SupervisorProbeResult {
|
||||
/** The lease-broker supervisor artifacts (launcher + daemon) are present. */
|
||||
readonly supervisorPresent: boolean;
|
||||
/** Resolved broker socket path, or null if it could not be resolved. */
|
||||
readonly socketPath: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Real supervisor/socket resolution: checks that the lease-broker's launcher
|
||||
* (`launch-runtime.py`) and supervisor (`daemon.py`) artifacts resolve on
|
||||
* disk via the same tool-resolution `execLeaseGatedRuntime()` uses, and that
|
||||
* a broker socket path resolves via the same logic as
|
||||
* `defaultLeaseBrokerSocket()`. Detection only — this never starts the
|
||||
* daemon and never connects to the socket.
|
||||
*/
|
||||
export function defaultSupervisorProbe(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): SupervisorProbeResult {
|
||||
const launcherPath = resolveTool('lease-broker', 'launch-runtime.py');
|
||||
const daemonPath = resolveTool('lease-broker', 'daemon.py');
|
||||
const supervisorPresent = existsSync(launcherPath) && existsSync(daemonPath);
|
||||
|
||||
let socketPath: string | null = null;
|
||||
try {
|
||||
const resolved = defaultLeaseBrokerSocket(env);
|
||||
socketPath = resolved.trim().length > 0 ? resolved : null;
|
||||
} catch {
|
||||
socketPath = null;
|
||||
}
|
||||
|
||||
return { supervisorPresent, socketPath };
|
||||
}
|
||||
|
||||
// ─── Predicate ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Injectable inputs for {@link leaseEnforcementActivatable}, so tests (and
|
||||
* downstream callers such as the C2 install-ordering guard) can drive every
|
||||
* branch without a live broker or an installed CLI on PATH. */
|
||||
export interface ActivationProbeDeps {
|
||||
getCapability?: () => LeaseActivationCapability | null;
|
||||
probeSupervisor?: () => SupervisorProbeResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* True IFF lease enforcement can actually be ACTIVATED on this host:
|
||||
*
|
||||
* (a) the resolvable CLI advertises a {@link LeaseActivationCapability}
|
||||
* compatible with {@link LEASE_ACTIVATION_CAPABILITY}, AND
|
||||
* (b) the broker supervisor is resolvable — launcher + `daemon.py`
|
||||
* artifacts present AND a broker socket path resolves.
|
||||
*
|
||||
* Pure/testable: both probes default to the real, side-effect-free lookups
|
||||
* above but can be injected, so this predicate never itself starts a broker
|
||||
* or performs enforcement — it only reports whether activation *could*
|
||||
* satisfy enforcement if wired.
|
||||
*/
|
||||
export function leaseEnforcementActivatable(deps: ActivationProbeDeps = {}): boolean {
|
||||
const getCapability = deps.getCapability ?? defaultCapabilityProbe;
|
||||
const probeSupervisor = deps.probeSupervisor ?? defaultSupervisorProbe;
|
||||
|
||||
if (!capabilityMatches(getCapability())) return false;
|
||||
|
||||
const supervisor = probeSupervisor();
|
||||
return supervisor.supervisorPresent && supervisor.socketPath !== null;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* C-REGRESS (issue #869, Point-1) — proves the fail-closed gate is untouched
|
||||
* by the C1 activation probe added alongside this test.
|
||||
*
|
||||
* `mutator-gate.py`'s fail-closed-on-absent-identity behavior is INTENTIONAL
|
||||
* and TEST-LOCKED: #869 C1 gates the WIRING decision for enforcement (via
|
||||
* `leaseEnforcementActivatable()`), it does not — and must not — touch the
|
||||
* gate's own runtime denial behavior. This spec runs the two test-locked
|
||||
* cases from `runtime_tools_unittest.py` directly (rather than merely
|
||||
* re-asserting the same logic in TypeScript) so a regression in the actual
|
||||
* Python gate is caught here too, not just documented in prose.
|
||||
*/
|
||||
|
||||
const MUTATOR_GATE_DIR = new URL('.', import.meta.url).pathname;
|
||||
const UNITTEST_FILE = join(MUTATOR_GATE_DIR, 'runtime_tools_unittest.py');
|
||||
|
||||
const LOCKED_TEST_CASES = [
|
||||
'ExecutableEntrypointTest.test_gate_entrypoint_denies_when_identity_environment_is_absent',
|
||||
'MutatorGateTest.test_environment_generation_and_request_failures_deny',
|
||||
] as const;
|
||||
|
||||
describe('mutator-gate fail-closed behavior (C-REGRESS, unchanged by #869 C1)', () => {
|
||||
it.each(LOCKED_TEST_CASES)('%s still passes', (testCase) => {
|
||||
const result = spawnSync('python3', ['-m', 'unittest', `${moduleName()}.${testCase}`, '-v'], {
|
||||
cwd: MUTATOR_GATE_DIR,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
expect(result.status, `stderr:\n${result.stderr}`).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
function moduleName(): string {
|
||||
// runtime_tools_unittest.py, addressed as a bare module name for `python3 -m unittest`.
|
||||
return UNITTEST_FILE.split('/').pop()!.replace(/\.py$/, '');
|
||||
}
|
||||
Reference in New Issue
Block a user