forked from mosaicstack/stack
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f11f257368 | ||
|
|
6c779595e1 | ||
|
|
cc097f36c5 | ||
|
|
56d8e8a3d5 | ||
|
|
4589659bc1 | ||
|
|
357a636a3a | ||
|
|
9e205e8201 | ||
|
|
73a313301b | ||
|
|
eddf718a5c | ||
|
|
c83a3cd3e2 | ||
|
|
bf8c6f4a89 | ||
|
|
bdf8932328 | ||
|
|
16d11cd6cd | ||
|
|
8ce77fcb0d | ||
|
|
cdd5568adb | ||
|
|
ff4b45b025 | ||
|
|
f47cf45b0c | ||
|
|
e7c9160c7b | ||
|
|
2d58779675 | ||
|
|
cbbe3e1ce2 | ||
|
|
9a7ab73952 | ||
|
|
2b85afe4ea | ||
|
|
eabe04bc5e | ||
|
|
36018be8d1 | ||
|
|
8c496993e4 | ||
|
|
e85a088f85 | ||
|
|
8a795df0ce | ||
|
|
f5a0566198 | ||
|
|
1e7a0701fd | ||
|
|
43f69bf167 | ||
|
|
f9435c2a03 | ||
|
|
3b191b6b34 | ||
|
|
85bdbe3383 | ||
|
|
ae4baf145d | ||
|
|
4512312b9f | ||
|
|
e233f196b5 | ||
|
|
3477e933df | ||
|
|
d0ad6e942b | ||
|
|
7f686aaf6d | ||
|
|
f13222b76b | ||
|
|
d1e7ba19ca | ||
|
|
0ffdcf14bd | ||
|
|
be10bdc828 | ||
|
|
2201c30284 | ||
|
|
75dfe2fa75 | ||
|
|
409bf23e6a | ||
|
|
31c3191305 | ||
|
|
6a067174ed | ||
|
|
01e966f36d | ||
|
|
524146055d | ||
|
|
06e0d40352 | ||
|
|
166ee8c90f | ||
|
|
826a8b3b26 | ||
|
|
a4280b9c98 | ||
|
|
4fb44f6345 |
@@ -4,6 +4,14 @@ 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/
|
||||
|
||||
@@ -41,6 +41,11 @@ steps:
|
||||
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
|
||||
# Test-membership guard (#1017): also first link of test:framework-shell.
|
||||
# Invoked from BOTH surfaces it audits (F2, PR #1018) — the guard is link
|
||||
# [0] of the pnpm chain, so severing that chain would silence it together
|
||||
# with everything it guards; this direct line keeps one instrument running.
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
|
||||
|
||||
# Blocking gate (#791): a framework upgrade must never write or delete an
|
||||
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<!-- board-roll: 1 entry rolled from BOARD.md -->
|
||||
|
||||
### **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.
|
||||
|
||||
<!-- board-roll: 2 entries rolled from BOARD.md -->
|
||||
|
||||
### **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-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).
|
||||
|
||||
<!-- board-roll: 2 entries rolled from BOARD.md -->
|
||||
|
||||
### **D-5 / P-QUEUE-001 + P-CONFORMANCE-001 — KEYSTONE: an inert gate that erased its own evidence.**
|
||||
|
||||
Merged PR #868 (`b79336a8`) shipped a file that FAILS `pnpm format:check` ⇒ the CI format gate did not block. An unrelated later PR (#872) then reformatted that file via its own `lint-staged`, so `main` went green again and nobody learned the gate had failed to fire. Verified blob-level under the repo's own config. **Detection must be per-merge-commit against that commit's own tree** — a "is main green today" check reports all-clear on this exact defect. Binding on RM-02/RM-55. Full chain in `TASKS.md` §1a. NOT quiet-patched, by Mos's ruling: patching the symptom destroys the signal.
|
||||
|
||||
### **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.
|
||||
|
||||
<!-- board-roll: 2 entries rolled from BOARD.md -->
|
||||
|
||||
### **D-7 / P-FLEET-001 — stale-GC-on-disk: shared 30G /tmp hit 100% ENOSPC, degrading two seats.**
|
||||
|
||||
~5.2G was session scratch dead 8-9 days (this session's own footprint: 88K). Same missing capability as orphaned-tmux-session GC, applied to disk — not a quota or discipline problem. Resolved manually by Mos (lead coordinator) after independent verification; `/tmp` now 79%. **The gap IS the finding:** the authority to reap exists, the deterministic reaper does not. Folded into RM-50 with explicit requirements (mechanical liveness, age threshold, dry-run, audit event per reap — never a heuristic sweep). Refusing to unilaterally delete another session's scratch was correct doctrine; the fix is a reaper, not braver agents.
|
||||
|
||||
### **D-6 / P-QUEUE-001 — the mandated queue guard returned PASS on an UNKNOWN state, live, today.**
|
||||
|
||||
Running the required `ci-queue-wait.sh --purpose push` before pushing produced `state=unknown ... exit 0` — the exact defect at `ci-queue-wait.sh:282-288` that PR #1023 is parked on. It also evaluated `branch=main` rather than the branch being pushed. The mission's own required pre-push gate passed me on an indeterminate result. Third independent live instance of the class.
|
||||
|
||||
<!-- board-roll: 1 entry rolled from BOARD.md -->
|
||||
|
||||
### **D-8 / P-CONFORMANCE-001 — a PRE-REGISTERED acceptance check that was not runnable as written.**
|
||||
|
||||
PR #1025 AC2's fixture `mkdir -p apps/*/venv/lib` creates a literal `apps/*/venv/lib` dir when the glob is unmatched — it did not test what it claimed. rev-974 ran it exactly as written, caught it, re-ran the intended assertion at an explicit path, and **disclosed** rather than silently substituting a working fixture and reporting PASS. **Pre-registration protects a check from being retrofitted to the implementation; it does not make the check correct.** An unverified gate appeared inside the mechanism built to catch unverified gates. Hard requirement on RM-02: the registry must self-verify that every registered case runs AND can fail — presence is not evidence.
|
||||
@@ -0,0 +1,102 @@
|
||||
# mos-remediation — LIVE BOARD (keep < 8 KB)
|
||||
|
||||
**Phase:** EXECUTING — RM-03 at owner-merge; RM-02 blocked on RM-61; RM-61 building.
|
||||
**Updated:** 2026-08-01 — **ORCHESTRATOR ROTATION SEAM** (prior seat rotated at ~803k tokens, ~4x threshold).
|
||||
|
||||
## 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 DONE** — checkout repaired, all three gates green HONESTLY (no `--no-verify`), branch pushed.
|
||||
- **TASK-1 DONE** — both planners delivered independently on clean context; reconciled into `TASKS.md`
|
||||
(58 tasks across P0–P5, 7 convergences, 7 adjudicated disagreements, 3 escalated decisions).
|
||||
- **NEXT ACTION IS NOT MINE:** DECISION-1/2/3 (`TASKS.md` §5) must be ruled before P0 dispatch.
|
||||
RM-01 is dispatchable immediately regardless — it depends on nothing and blocks everything.
|
||||
|
||||
## In-flight
|
||||
|
||||
| Task | Owner | State |
|
||||
| ----------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| RM-01 reproducible checkout | — | **MERGED** `f58b3699` (#1027) |
|
||||
| RM-03 queue guard | **Jason** | **merge-gate GO** @ `78ec47cd` (comment 20392) — **HELD FOR OWNER MERGE**. Head verified unmoved; GO is commit-bound and VOID if it moves — **do not push to #1032** |
|
||||
| RM-02 gate registry ★keystone | — | **BLOCKED on RM-61.** Code complete @ `f9746b23`, own harness green in CI, 2 reviews clear, head FROZEN. Cannot certify terminal-green: `ci-postgres` FAIL on #2187 **and** #2188 |
|
||||
| RM-61 CI-contract exemption | coder-mos1 | design APPROVED, **building**. Controls first → prove discrimination → then exempt |
|
||||
| RM-59 / RM-60 | Jason (infra) | tracked deps; RM-60 option **B** (A does not fix the ordering defect) |
|
||||
| #1023 queue-guard attempt | Jason | SUPERSEDED-PENDING-JASON — RM-03 supersedes; live REQUEST_CHANGES, do **not** merge |
|
||||
|
||||
### For the incoming orchestrator — read this before acting
|
||||
|
||||
1. **Nothing is waiting on you that is urgent.** RM-03 waits on Jason; RM-02 waits on RM-61; RM-61 is
|
||||
building. Read the record before touching any lane.
|
||||
2. **`docs/remediation/TASKS.md` is authoritative**, not the newest voice in a chat. It holds 35 findings
|
||||
(D-1…D-6 in `BOARD-LEDGER.md`, D-7…D-35 in TASKS.md), every ruling with its rationale, and the
|
||||
requirements each finding placed on RM-02/RM-34/RM-50/RM-55.
|
||||
3. **`MISSION.md` carries five first-class principles**, all earned by live failures — observe the
|
||||
property not the proxy · pre-registration prevents retrofitting and nothing else · never ship an
|
||||
integrity claim dressed as a property · when a property cannot exist at its layer, bound it and track
|
||||
the real guarantee · query for refutation, never for confirmation · redundant observation on
|
||||
evidence-bearing steps.
|
||||
4. **Seat identity:** `export MOSAIC_GIT_IDENTITY=<seat>` is stripped by a context reset (**D-34**) —
|
||||
every dispatch/rehydration brief must re-export it, or the seat cannot use its credentials.
|
||||
5. **Scan CI from `-f json`, never default text** — text mode omits `clone` (**D-33**). State counts.
|
||||
6. **The queue guard is zero-information until RM-03 merges** (**D-23**) — never cite its green.
|
||||
7. **The bounded CI re-roll used on RM-02 was a one-time stopgap, NOT policy.** A per-PR free re-roll is
|
||||
D-21 normalisation. Do not repeat it; RM-61 is the fix.
|
||||
|
||||
## Delivery gates — REFERENCE, do not restate
|
||||
|
||||
Canonical: `~/.config/mosaic/fleet/roles.local/merge-gate.md` (verdict authority) and
|
||||
`~/.config/mosaic/fleet/roles/validator.md`. Order + the freeze/zero-information rules are stated once in
|
||||
[`MISSION.md`](./MISSION.md) and [`KICKSTART.md`](./KICKSTART.md) — **read them there.**
|
||||
|
||||
This board deliberately does **not** repeat the gate definition: restating it from memory is exactly how
|
||||
the merge-gate step went missing from mission setup in the first place (**D-26**), twice, including once
|
||||
inside the correction for it.
|
||||
|
||||
## Fleet seats
|
||||
|
||||
- mos-remediation — project orchestrator (Claude, /src/mosaic-stack, socket `mosaic-fleet`) — ACTIVE
|
||||
- planner-opus — adversarial planner (robustness), Opus 5, socket `default` — DELIVERED, idle
|
||||
- planner-sol — adversarial planner (pragmatic), gpt-5.6-sol, socket `default` — DELIVERED, idle
|
||||
- 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
|
||||
|
||||
- Freeze: LIFTED for this workstream only.
|
||||
- Git identity: orchestrator runs `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.
|
||||
- Capability is **per-path** (D-11b → **superseded in part by D-13/D-15**): a token file is **necessary,
|
||||
not sufficient**. Three layers — token file (raw-API), `tea` login (tea paths), **repository permission**
|
||||
(writes). Before dispatch, assert `permissions.push == true` **as that seat**, not token existence and
|
||||
not a 200 on a read. Mos owns provisioning; escalate missing pairs.
|
||||
- Seat identity (D-11a): token identity AND `git config user.name`/`user.email` must BOTH be set and
|
||||
agree. Exporting `MOSAIC_GIT_IDENTITY` alone does NOT fix commit authorship.
|
||||
- Standing worker-brief doctrine (mandatory in EVERY brief): re-export `MOSAIC_GIT_IDENTITY` (**D-34**);
|
||||
commit early/WIP (**D-31**); don't weaken a RED test to pass; if a check is unrunnable SAY SO, never
|
||||
substitute; `agent-send -f` never `-m`; artifacts off shared `/tmp`; scan CI from `-f json` (**D-33**).
|
||||
- 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 — see [`MISSION.md`](./MISSION.md)
|
||||
|
||||
Builds 1-5 and the cross-cutting retirements are stated once in the charter. **Not repeated here.**
|
||||
|
||||
⚠ **DECISION-1 is RULED, not contested** (this board previously said otherwise — stale for hours after the
|
||||
ruling). The choke point targets a **new production Node `TaskExecutor` on the LIVE dispatch path**
|
||||
(`packages/mosaic` launch + `packages/coord`); the disabled Python rail is **deleted, not ported**.
|
||||
|
||||
## Decisions log — full record in [`TASKS.md`](./TASKS.md)
|
||||
|
||||
All 35 findings (D-1…D-6 in `BOARD-LEDGER.md`, D-7…D-35 in `TASKS.md`) and every ruling with its
|
||||
rationale live there. **Not duplicated here** — a second copy is a second thing to go stale, which this
|
||||
board has now done three times in one night (gate list, capability registry, DECISION-1 status).
|
||||
|
||||
Live rulings a fresh seat most needs:
|
||||
|
||||
- **D-23** queue guard is inert → zero-information until RM-03 merges. Never cite its green.
|
||||
- **D-33** scan CI from `-f json`; text mode omits `clone`. State counts.
|
||||
- **D-34** a context reset strips `MOSAIC_GIT_IDENTITY` → re-export in every brief.
|
||||
- **RM-61** exemption: controls FIRST, prove discrimination, THEN adopt — or fall to option A.
|
||||
- The RM-02 bounded CI re-roll was a **one-time stopgap, never policy**.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,424 @@
|
||||
# Adversarial Decomposition — Pragmatic / Shortest-Path Side
|
||||
|
||||
**Planner:** `planner-sol`
|
||||
**Bias:** make one real fleet task pass through one enforced path as early as possible; reuse before building.
|
||||
**Scope source:** `MISSION.md`, `MACP-WIRING-SCOUT.md`, `BOARD.md`, existing `@mosaicstack/macp`, `packages/coord`, PG/Valkey, Tess durable inbox/outbox, and the Mission Control PRD.
|
||||
|
||||
## Executive position
|
||||
|
||||
The first useful milestone is **not** “complete Builds 1 and 2.” It is this narrow vertical slice:
|
||||
|
||||
> A DB-backed mission task is atomically claimed by `packages/coord`, executed by one Node `@mosaicstack/macp` TaskExecutor, gated, and terminally recorded with identity-bound events and a tri-state mutation result. No `docs/TASKS.md`, `mission.json`, `tasks.json`, Python gate loop, or NDJSON ledger participates.
|
||||
|
||||
That slice is **SOL-03 → SOL-04 → SOL-05 → SOL-06 → SOL-07 → SOL-08**, estimated at **72K tokens**, mostly Codex. PG polling is acceptable for this first proof. Redis acceleration follows only after correctness is observable. This is the shortest path that is both dogfoodable and not throwaway work.
|
||||
|
||||
### Cost posture
|
||||
|
||||
- **25 PR tasks, ~294K tokens total:** ~164K Codex, ~130K Sonnet, **0K Opus**.
|
||||
- First live choke-point dogfood: ~72K on the hard path; SOL-01 and SOL-02 can run beside it.
|
||||
- Opus is not justified for planned implementation. Escalate only if an independent security review finds an unresolved architecture-level authority flaw.
|
||||
- Every row is one PR. Estimates include implementation, focused tests, docs affected by that PR, and one remediation pass—not orchestration/reviewer overhead.
|
||||
|
||||
## Gates and critical path
|
||||
|
||||
| gate | opens when | proof required before downstream work |
|
||||
| ------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| **G0 — trustworthy launch gates** | SOL-01, SOL-02 | non-root checkout works; queue status cannot become false-green |
|
||||
| **G1 — first dogfood / minimum viable cut** | SOL-08 | one live fleet task completes DB → MACP executor → gates → DB with no flat-file state |
|
||||
| **G2 — Builds 1+2 closed** | SOL-09..SOL-12 | all producers use the executor; duplicate islands retired; Redis loss is recoverable from PG |
|
||||
| **G3 — rotation real** | SOL-13..SOL-16 | stale generation cannot mutate; fresh session resumes typed state; Pi-brick recovery works without broker |
|
||||
| **G4 — sole-path comms real** | SOL-17..SOL-22 | roster identity is stable; bounced/stale messages converge through PG/Redis and adapters |
|
||||
| **G5 — mission proof** | SOL-23..SOL-25 | workflow sweep cannot capture unknown files; fault bank passes, including 100 rotations |
|
||||
|
||||
**Critical path:** `03 → 04 → 05 → 06 → 08 → 10 → 12 → 13 → 14 → 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22 → 24 → 25`.
|
||||
|
||||
## Ordered task list
|
||||
|
||||
### SOL-01 — Repair activation coherence and non-root checkout hygiene
|
||||
|
||||
- **build:** 5 (hygiene; pulled forward)
|
||||
- **depends_on:** —
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. A clean non-root checkout can run dependency/bootstrap preparation without accessing `/root`.
|
||||
2. A root CI checkout still uses an isolated writable pnpm store.
|
||||
3. Activation installs CLI, hooks, broker/runtime assets, and version manifest transactionally: induced failure leaves the prior complete generation active.
|
||||
4. Launch with a deliberately skewed component version is rejected with the exact repair command; diagnostics remain usable.
|
||||
5. No test uses `--no-verify` or suppresses hooks.
|
||||
- **dogfood seed:** BOARD D-1 root-pinned `.npmrc` and D-2 root-owned Husky path.
|
||||
- **est. tokens:** 6K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-02 — Make queue guard fail-safe with exit-asserting tests
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** —
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Fixture responses `pending`, `success`, `failure`, no-status, malformed JSON, provider error, and unknown status produce explicitly asserted process exits.
|
||||
2. Only terminal success/no-active-queue returns 0; unknown, malformed, and transport failure return non-zero with actionable output.
|
||||
3. A payload larger than 150 KiB is consumed without argv expansion or truncation.
|
||||
4. At least one mutant changes unknown→success and is killed by the test suite.
|
||||
- **dogfood seed:** inert gate-6 and recursive #1019 failure (unknown→exit 0; ARG_MAX).
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-03 — Complete the canonical MACP contract, not another protocol
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** —
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. `@mosaicstack/macp` validates typed Task, TaskResult, lifecycle Event, state Claim, and `verified | written-unverified | failed` mutation outcome records.
|
||||
2. Claims require source, confidence, issued-at, TTL/expiry, refresh instruction, and HMAC integrity; tamper/expiry returns a typed refusal, never partial data.
|
||||
3. Lifecycle events include launch, mission generation, checkpoint, rotation, recovery, inbox receipt, and terminal disposition while preserving existing task events.
|
||||
4. `MOSAIC_AGENT_NAME` is required for mutating execution and appears in credential/actor binding; missing identity fails closed.
|
||||
5. Target metadata requires repository identity, task/record ID, and head or generation where applicable.
|
||||
- **dogfood seed:** rev-974 identity drift plus the three observed write outcomes.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-04 — Add the narrow PG orchestration spine schema
|
||||
|
||||
- **build:** 2
|
||||
- **depends_on:** SOL-03
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Migration up creates mission, task, dependency, task-claim, MACP event, typed state-claim, session-generation, and dispatch-outbox records with tenant/mission keys and uniqueness constraints.
|
||||
2. The database rejects a task without a mission, a dependency outside its mission, duplicate idempotency keys, and terminal→running regression.
|
||||
3. Event and claim records reference canonical mission/task/generation identities; claims store integrity metadata.
|
||||
4. Migration rollback on an empty test DB succeeds; rerunning migration is safe.
|
||||
5. No comms-specific “universal message” schema is invented here; Build 4 reuses/extends existing interaction inbox/outbox tables.
|
||||
- **dogfood seed:** mission convention existed but a lane could act with no mechanically valid mission/task.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-05 — Implement atomic PG task claims, transitions, ledger, and outbox
|
||||
|
||||
- **build:** 2
|
||||
- **depends_on:** SOL-04
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Two concurrent claimers for one runnable task yield exactly one lease owner.
|
||||
2. Dependencies are evaluated transactionally; an unmet dependency can never be claimed.
|
||||
3. Claim, state transition, MACP event, and dispatch-outbox append commit atomically or all roll back.
|
||||
4. Expired leases are reclaimable with a higher fencing generation; stale owners cannot complete or mutate.
|
||||
5. Querying mission status is derived solely from PG and returns the next runnable task deterministically.
|
||||
- **dogfood seed:** model-maintained live board and stale claims surviving session changes.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-06 — Build the one production Node MACP TaskExecutor
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** SOL-03, SOL-05
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. A public Node executor accepts only a claimed canonical MACP Task, resolves credentials/identity, runs the worker, runs structured gates, and persists terminal result/events through SOL-05.
|
||||
2. Worker exit 0 plus a failed gate cannot produce `completed`; worker failure cannot skip terminal ledger emission.
|
||||
3. Claude, Codex, and Pi fixture backends emit the same runtime-neutral lifecycle sequence.
|
||||
4. Every mutation returns one mandatory tri-state outcome; callers cannot compile while discarding it.
|
||||
5. Crash after worker success but before terminal commit leaves a recoverable fenced claim and no false completion.
|
||||
- **dogfood seed:** stranded MACP, gate-6 inert completion, and `written-unverified` being treated as success.
|
||||
- **est. tokens:** 16K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-07 — Provide one-shot flat-file import and cutover readiness audit
|
||||
|
||||
- **build:** 2
|
||||
- **depends_on:** SOL-05
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. A dry-run parses existing mission/TASKS artifacts, reports unsupported/ambiguous rows, and performs zero writes.
|
||||
2. Apply is idempotent and records source digests; repeated apply creates no duplicates.
|
||||
3. Unknown status, dangling dependency, duplicate task ID, and malformed table block import with row-level diagnostics.
|
||||
4. Readiness reports “cutover-ready” only when imported PG projections exactly match source counts/dependencies/statuses.
|
||||
5. This command is migration-only; it exposes no dual-write or ongoing sync mode.
|
||||
- **dogfood seed:** current remediation board/TASKS state needs a clean DB landing without silently losing tasks.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-08 — Hard-cut `packages/coord` to PG and dogfood one live task
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** SOL-06, SOL-07
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. `mosaic coord run/status/continue` reads and mutates PG only; absent/unmigrated DB state fails with the SOL-07 repair path.
|
||||
2. No fallback reads/writes `docs/TASKS.md`, mission JSON, task JSON, state JSON, results JSON, or events NDJSON.
|
||||
3. A live canary task assigned to a fleet seat travels PG claim → TaskExecutor → worker → gate → terminal PG result/event and closes only after gate success.
|
||||
4. Killing the coordinator after claim and restarting it neither duplicates execution nor allows the stale lease to close the task.
|
||||
5. Evidence query shows actor seat, mission/task, target metadata, gate results, and tri-state outcome.
|
||||
- **dogfood seed:** this remediation mission itself; reproduce a gate-6-style non-null task and identity-bound write.
|
||||
- **est. tokens:** 16K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
> **G1 FIRST-DOGFOOD:** stop and validate here before broadening. If SOL-08 cannot carry a real task, do not build Redis, rotation, comms, or UI.
|
||||
|
||||
### SOL-09 — Route Forge and OpenClaw/MACP producers through TaskExecutor
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** SOL-08
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Forge and the OpenClaw MACP runtime submit the canonical Task type to SOL-06; neither executes a worker or gate itself.
|
||||
2. Their success callbacks are derived from canonical terminal results, not local/stub completion.
|
||||
3. A failed canonical gate is observed identically from Coord, Forge, and OpenClaw fixtures.
|
||||
4. Repository search plus an executable import boundary test finds no production-local redefinition of Task/TaskResult/GateResult on these paths.
|
||||
- **dogfood seed:** Forge’s immediate empty-gate completion and the plugin’s redefined MACP-shaped result.
|
||||
- **est. tokens:** 10K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-10 — Retire flat-file orchestration and the disabled duplicate rail
|
||||
|
||||
- **build:** 1
|
||||
- **depends_on:** SOL-09
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. The Python controller execution/gate/event path, `tasks_md_sync`, plugin-local protocol types, orphaned context loader, and production flat-file orchestration writers/readers are absent from shipped assets.
|
||||
2. Framework guides/templates/startup context point to DB mission commands, not `docs/TASKS.md` as orchestration SoR.
|
||||
3. A regression scan fails CI if production code reintroduces `events.ndjson`, `tasks.json`, `mission.json`, or `docs/TASKS.md` orchestration mutation.
|
||||
4. jarvis-brain PDA flat files and unrelated project docs remain untouched.
|
||||
5. Upgrade removes/quarantines obsolete generated rail files without deleting user source/docs.
|
||||
- **dogfood seed:** three parallel islands and stale `.mosaic/orchestrator/mission.json` 0/0 residue.
|
||||
- **est. tokens:** 14K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-11 — Add Redis hot dispatch as a derived outbox consumer
|
||||
|
||||
- **build:** 2
|
||||
- **depends_on:** SOL-08
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Task creation commits mission/task/outbox in PG before any Redis enqueue.
|
||||
2. Induced Redis failure leaves the task durable and pending; a sweeper later enqueues it exactly once logically.
|
||||
3. Deleting the Redis queue and rebuilding from PG restores all non-terminal dispatches without reviving terminal tasks.
|
||||
4. Duplicate delivery is neutralized by PG claim fencing/idempotency.
|
||||
5. Existing `packages/queue` adapter/config is reused; no second broker API is introduced.
|
||||
- **dogfood seed:** inert/unknown queue transport and broker outage during task dispatch.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-12 — Lock Builds 1+2 with black-box failure cases
|
||||
|
||||
- **build:** 2
|
||||
- **depends_on:** SOL-02, SOL-10, SOL-11
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. A black-box suite proves: unknown queue status blocks; malformed task blocks; gate failure blocks completion; dropped identity blocks mutation; HMAC corruption forces refresh; Redis loss recovers from PG; stale lease cannot close.
|
||||
2. The suite invokes shipped CLI/service boundaries, not internal mocks.
|
||||
3. Every asserted failure checks process/result status and durable terminal/non-terminal state.
|
||||
4. The same canary task succeeds under Claude, Codex, and Pi adapters or a documented unavailable-runtime fixture fails explicitly.
|
||||
- **dogfood seed:** gate-6/#1019, identity drift, and built-but-unwired MACP.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-13 — Bind session authority to contract hash and generation
|
||||
|
||||
- **build:** 3
|
||||
- **depends_on:** SOL-12
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Launch computes a stable hash over the effective Constitution/AGENTS/runtime/skills set and stores it with session generation.
|
||||
2. Policy change or compaction detection marks the generation stale before any subsequent mutation.
|
||||
3. A stale/mismatched generation can read diagnostics but cannot claim, write task state, acknowledge comms, merge, or close.
|
||||
4. Re-attestation creates a new generation; old credentials/leases remain fenced.
|
||||
5. Hash input order/path normalization is deterministic across two clean launches.
|
||||
- **dogfood seed:** compacted orchestrator losing directives and D-4 ignoring an in-message reset.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-14 — Persist compact typed rotation checkpoints using Coord primitives
|
||||
|
||||
- **build:** 3
|
||||
- **depends_on:** SOL-13
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Checkpoint contains mission/task, completed/blocked state, next three actions, constraints, claims, contract hash/generation, and cursors—never transcript text.
|
||||
2. Checkpoint is HMAC-verified before rehydration; corrupt/expired/missing required claims refuse resume and request deterministic refresh.
|
||||
3. Writing checkpoint and rotation-intent event is atomic in PG.
|
||||
4. Existing Coord continuation capsule semantics are reused; no competing handoff schema/file is created.
|
||||
- **dogfood seed:** manual MOS-ORCHESTRATION-BOARD checkpoint and incomplete-rehydration risk.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-15 — Finish the deterministic coordinator rotation daemon
|
||||
|
||||
- **build:** 3
|
||||
- **depends_on:** SOL-14
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Configured token threshold triggers checkpoint → revoke old authority → terminate → launch fresh → verify rehydration in that order.
|
||||
2. Compaction-detected is a backstop that forces the same rotation path; it never requests recursive compaction.
|
||||
3. A launch failure leaves the mission recoverable and visibly paused, not assigned to two active generations.
|
||||
4. Ephemeral seats die/respawn without mission checkpoint; persistent/orchestrator seats rotate.
|
||||
5. The implementation extends `packages/coord`; untracked `apps/coordinator` residue is not revived.
|
||||
- **dogfood seed:** planner-sol dirty-context dispatch and the old coordinator’s log-only `_check_context()` behavior.
|
||||
- **est. tokens:** 16K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-16 — Add broker-independent recovery and remove silent MOSAIC BYPASS
|
||||
|
||||
- **build:** 3
|
||||
- **depends_on:** SOL-15
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. With Redis/broker unavailable, a diagnostic/bootstrap command can inspect PG mission state, repair broker configuration, and resume without traversing the broker gate.
|
||||
2. Normal recovery remains broker-gated and is labeled as such.
|
||||
3. Break-glass requires explicit scope and expiry, emits a durable event, displays a loud banner, and auto-expires; permanent/silent bypass text or behavior is absent.
|
||||
4. The Pi-brick fixture recovers the broker, then returns to normal gated operation without editing source/config by hand.
|
||||
5. Orchestrator guidance removes “/compact and continue” only after the rotation command is available; ephemeral guidance remains explicit.
|
||||
- **dogfood seed:** Pi brick and silent `MOSAIC BYPASS 2026-07-22`.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-17 — Converge each host on one roster-owned lifecycle domain
|
||||
|
||||
- **build:** 5 (hygiene; hard prerequisite for addressed comms)
|
||||
- **depends_on:** SOL-16
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Reconcile establishes exactly one roster-declared tmux socket/lifecycle domain per host.
|
||||
2. Unknown sessions are reported and quarantined; they are never killed without positive unmanaged classification.
|
||||
3. Max-age/max-context stale sessions invoke SOL-15 rotation for persistent seats or reap for ephemerals.
|
||||
4. Seat identity survives respawn and equals the roster/MOSAIC_AGENT_NAME binding.
|
||||
5. A fixture matching the current four unmanaged remediation seats converges them or produces explicit quarantine actions.
|
||||
- **dogfood seed:** scout-bounce and BOARD D-3 seats split between default and `mosaic-fleet` sockets.
|
||||
- **est. tokens:** 14K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-18 — Publish authenticated `comms/v1` envelope and compatibility rules
|
||||
|
||||
- **build:** 4
|
||||
- **depends_on:** SOL-13, SOL-17
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Envelope validates protocol version, message/idempotency ID, sender/recipient seat identity, class, ordering/coalesce key, creation/expiry, correlation, payload digest, and authentication.
|
||||
2. Current and immediately previous supported protocol versions are accepted; unsupported versions are rejected loudly with supported range.
|
||||
3. Framework/runtime version is diagnostic metadata and never the compatibility key.
|
||||
4. Forged sender, changed recipient/payload, expired envelope, and replay with conflicting content fail closed.
|
||||
- **dogfood seed:** wrong-socket bare tmux message with no authoritative sender/recipient receipt.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-19 — Build the logical PG-first comms service with tmux adapter
|
||||
|
||||
- **build:** 4
|
||||
- **depends_on:** SOL-18
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Sending commits envelope/payload and PENDING state in PG before adapter delivery.
|
||||
2. State machine enforces PENDING → RECEIVED → CONSUMED or DEAD-LETTER; illegal regressions are rejected.
|
||||
3. Recipient-filtered claims and append/coalesce policy are deterministic by message class.
|
||||
4. tmux is a dumb adapter: delivery failure changes no PG authority state and is retryable.
|
||||
5. Existing Tess durable repository/state-machine patterns are extended or generalized; no new deployable microservice or second inbox framework appears.
|
||||
- **dogfood seed:** MACP scout bounce that was discovered only by manual liveness check.
|
||||
- **est. tokens:** 16K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-20 — Make `agent-send` use the sole path and prove stale-message handling
|
||||
|
||||
- **build:** 4
|
||||
- **depends_on:** SOL-19
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Normal `agent-send` creates a comms/v1 record and observes RECEIVED/CONSUMED; it cannot directly invoke tmux.
|
||||
2. A wrong/missing socket leaves PENDING with retry diagnostics, then reaches RECEIVED after roster repair without resending.
|
||||
3. A stale coalescible message arriving after a newer terminal message is marked superseded/consumed and is not surfaced as live work.
|
||||
4. Duplicate identical send is idempotent; same ID with changed content is rejected.
|
||||
5. Inbox receipt/terminal disposition emits canonical MACP lifecycle events.
|
||||
- **dogfood seed:** scout-bounce and #1018 stale-consumed message arriving after merge.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-21 — Add Redis Streams hot delivery and PG reconciliation
|
||||
|
||||
- **build:** 4
|
||||
- **depends_on:** SOL-11, SOL-20
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. PG commit precedes XADD; induced XADD failure is repaired by sweeper.
|
||||
2. Consumer uses a PEL; ack sequence is PG CONSUMED commit before XACK.
|
||||
3. Redis flush/restart rebuilds pending delivery from PG without duplicating consumed messages.
|
||||
4. Pending, abandoned, and dead-letter transitions are observable with bounded retry/backoff.
|
||||
5. Existing Redis/queue connection/configuration is reused.
|
||||
- **dogfood seed:** delivery bounce plus broker loss between durable write and hot enqueue.
|
||||
- **est. tokens:** 12K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-22 — Prove adapter pluggability with the existing Matrix connector
|
||||
|
||||
- **build:** 4
|
||||
- **depends_on:** SOL-21
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Existing Matrix connector consumes/produces comms/v1 through SOL-19 without owning authority state.
|
||||
2. The same envelope can fail tmux and later deliver through Matrix while producing one logical message lifecycle.
|
||||
3. Matrix retry/reconnect cannot regress PG state or duplicate CONSUMED work.
|
||||
4. Removing Matrix availability leaves PG/Redis/tmux behavior intact.
|
||||
- **dogfood seed:** cross-socket scout notification bounce; alternate reach must not become alternate authority.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-23 — Constrain auto-sync and agent writes by allowlist and lease
|
||||
|
||||
- **build:** 5
|
||||
- **depends_on:** SOL-10
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Auto-sync stages only an explicit allowlist; an unknown modified/untracked docs/source file remains unstaged and is reported.
|
||||
2. Agent source/docs writes require the correct worktree/lease; two seats cannot acquire the same mutable target concurrently.
|
||||
3. Generated files are positively identified, not inferred by denylist.
|
||||
4. The measured annotation/index mid-write fixture cannot be swept into an unrelated commit.
|
||||
5. DB orchestration state is absent from repository staging concerns.
|
||||
- **dogfood seed:** auto-sync sweep commit `517bd5c26` capturing agent-authored docs mid-write.
|
||||
- **est. tokens:** 8K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
### SOL-24 — Build the real-artifact lifecycle conformance harness
|
||||
|
||||
- **build:** 5
|
||||
- **depends_on:** SOL-16, SOL-17, SOL-22, SOL-23
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Harness launches shipped CLI/runtime artifacts and fault-injects compaction, broker outage, delivery bounce, identity drop, stale contract hash, queue unknown/malformed, Redis loss, and auto-sync collision.
|
||||
2. One deterministic test executes 100 sequential rotations with no lost/duplicated task, claim, receipt, or terminal disposition.
|
||||
3. Tests assert DB state/event order and process exits, not log substrings alone.
|
||||
4. Harness runs against isolated PG/Redis namespaces and cleans only resources it created.
|
||||
5. Every banked dogfood seed has a named case and evidence output suitable for CI/release attachment.
|
||||
- **dogfood seed:** the complete failure bank: Pi brick, scout-bounce, gate-6/#1019, identity drift, auto-sync, #1018 stale-consumed, D-4 dirty context.
|
||||
- **est. tokens:** 18K
|
||||
- **suggested runtime tier:** sonnet
|
||||
|
||||
### SOL-25 — Complete operator cutover docs and activation proof
|
||||
|
||||
- **build:** 5
|
||||
- **depends_on:** SOL-01, SOL-02, SOL-10, SOL-16, SOL-22, SOL-24
|
||||
- **acceptance criteria (diff-blind testable):**
|
||||
1. Operator docs give exact DB import/cutover, rollback-before-cutover, recovery, break-glass expiry, rotation, comms, quarantine, and conformance commands.
|
||||
2. Link/command checks find no orchestrator instruction to mutate flat-file mission/tasks, use silent bypass, direct-tmux normal comms, or “compact and continue” a persistent seat.
|
||||
3. A clean non-root install activates one coherent version and runs the conformance smoke subset.
|
||||
4. Release evidence maps all 15 decisions and every live seed to a passing check or an explicit deferred item below.
|
||||
- **dogfood seed:** activation skew plus the tendency to leave built fixes unwired or undocumented.
|
||||
- **est. tokens:** 6K
|
||||
- **suggested runtime tier:** codex
|
||||
|
||||
## Explicit DEFER list (10)
|
||||
|
||||
These are not rejected; they are **past first dogfood** and should not delay G1/G2. Each is gold-plating unless a live failure makes it necessary.
|
||||
|
||||
1. **DEFER — Mission dashboard/TUI views.** CLI/DB queries are enough to operate and prove the spine.
|
||||
2. **DEFER — PRD-to-board automatic decomposition.** This is LLM/judgment-heavy and unrelated to enforcing already-decided tasks.
|
||||
3. **DEFER — General heuristic churn scoring.** Implement token threshold + compaction sensor first; repeated-tool-loop inference can follow measured need.
|
||||
4. **DEFER — Discord comms adapter.** Existing plugin reach remains; migrate only after tmux+Matrix prove the service contract.
|
||||
5. **DEFER — Slack comms adapter.** No current dogfood dependency.
|
||||
6. **DEFER — Telegram comms adapter.** No current dogfood dependency.
|
||||
7. **DEFER — Public MCP comms surface.** `agent-send` and service API are sufficient for the mission proof.
|
||||
8. **DEFER — Protocol-v2 features/general negotiation framework.** Ship v1 with a bounded current/previous acceptance window; do not predict v2.
|
||||
9. **DEFER — Multi-region/HA PG or Redis.** Existing in-stack PG+Redis and rebuildability satisfy current failure classes.
|
||||
10. **DEFER — Event analytics/search UI and long-term warehouse.** Indexed PG evidence plus CLI queries is enough for audit/conformance.
|
||||
|
||||
## Suspect abstractions register
|
||||
|
||||
| proposed thing | verdict |
|
||||
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Canonical Node `TaskExecutor` | **JUSTIFIED:** explicitly required single choke point; wraps existing MACP functions rather than replacing them. |
|
||||
| PG repository methods | **JUSTIFIED but narrow:** ordinary adapters around existing Drizzle/DB patterns, not a new “state platform.” |
|
||||
| Rotation daemon | **JUSTIFIED:** finishes `packages/coord`; do not revive `apps/coordinator` or create another service. |
|
||||
| Comms service | **JUSTIFIED only as a logical in-process boundary:** reuse Tess durable inbox/outbox and existing connectors; no new deployable microservice this cycle. |
|
||||
| Universal queue/broker abstraction | **SUSPECT / DO NOT BUILD:** reuse `packages/queue`, PG outbox, and Redis Streams/BullMQ configuration already present. |
|
||||
| Universal envelope/state framework | **SUSPECT / DO NOT BUILD:** MACP Task/Claim/Event and comms/v1 have different bounded purposes. |
|
||||
| Generic compatibility-negotiation engine | **SUSPECT / DEFER:** a small supported-version check meets v1 needs. |
|
||||
|
||||
## Dissent (7)
|
||||
|
||||
1. **Do not wire new production behavior into `mosaic_orchestrator.py::run_single_task`.** The scout correctly identified the duplicated block, but BOARD’s later ruling says the disabled Python rail is residue and must be retired. Building a Node bridge only to delete it is throwaway. Put the Node TaskExecutor in `@mosaicstack/macp`, route the live Coord path to it at SOL-08, migrate remaining producers at SOL-09, then delete the Python block at SOL-10.
|
||||
2. **Redis is not on the first-dogfood critical path.** PG claim/polling is sufficient for one real task and exposes correctness earlier. Add Redis only after G1; otherwise queue debugging obscures whether the choke point works.
|
||||
3. **“One choke-point service” does not justify a new deployable service.** An exported executor plus Coord daemon is enough. A new Nest app, RPC protocol, deployment, auth layer, and health plane would be greenfield.
|
||||
4. **The Mission Control PRD’s file-first and board-regeneration assumptions are superseded.** Keep its mission/rotation semantics, but obey the accepted hard DB cutover; do not implement its file-first milestones or PRD-to-board generator now.
|
||||
5. **Do not gate every interactive runtime launch as if it were a mission task.** Enforce every tracked task/data mutation at the executor/DB authority boundary. Ephemeral interactive shells may launch, but receive no task mutation authority unless attached to a valid claim.
|
||||
6. **Do not implement broad “churn intelligence.”** Token threshold and compaction-detected are deterministic sensors. Repeated-loop semantic detection is expensive, noisy, and premature until telemetry demonstrates a gap.
|
||||
7. **The 100-rotation test is a final conformance bar, not an early unit-test tax.** First prove one rotation, then fault cases, then 100 repetitions in SOL-24. Requiring 100 before G3 would delay feedback without changing the design.
|
||||
|
||||
## Orchestrator reconciliation notes
|
||||
|
||||
- Pre-register each task’s acceptance checks from this document before showing implementation diffs to its reviewer. Author and reviewer remain different seats.
|
||||
- SOL-07 permits a one-time import, **not** an interim store: no shadow writes, dual reads, or sync daemon.
|
||||
- G1 is the budget escape hatch. If the 72K hard-path slice does not work, stop and remediate instead of spending the remaining ~222K.
|
||||
- Docs belong in each behavior-changing PR where required; SOL-25 is cross-link/cutover validation, not permission to postpone essential docs.
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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`, then **`git fetch origin remediation/state`**.
|
||||
⚠ **The live board is on the rolling branch `remediation/state`, NOT on `main`.** `main` carries only
|
||||
periodic snapshots, so reading the board from `main` will silently give you a STALE tick. Read the
|
||||
live files at `origin/remediation/state` (e.g. `git show origin/remediation/state:docs/remediation/BOARD.md`),
|
||||
or check that branch out. Every tick is pushed there immediately, so its HEAD is always the newest state.
|
||||
2. Read `docs/remediation/MISSION.md` — the charter (goal, 4 builds, 15 decisions, sequencing, directives).
|
||||
3. Read `docs/remediation/BOARD.md` **at `origin/remediation/state`** — the LIVE state: current phase,
|
||||
in-flight tasks, fleet seat assignments, gate status. Single source of in-flight truth (kept < 8 KB;
|
||||
older entries roll to `BOARD-LEDGER.md` via `board-roll.sh`).
|
||||
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 — REFERENCE the canonical files, never restate them:**
|
||||
`~/.config/mosaic/fleet/roles.local/merge-gate.md` (verdict authority) and
|
||||
`~/.config/mosaic/fleet/roles/validator.md` (validator role). Order:
|
||||
independent review (author ≠ reviewer, `rev-974`; pre-registered diff-blind checks committed before the
|
||||
diff is read) → remediation → **CI terminal-green at the exact head by full step scan** →
|
||||
**merge-gate verdict `GO`/`NO-GO`/`HOLD`**, commit-bound and **void the instant the head moves**, posted
|
||||
durably with enumerated evidence under its own minted identity → **coordinator head-pinned merge**.
|
||||
The queue guard runs but is **zero-information until RM-03 lands** (D-23) and must not be cited as evidence.
|
||||
**After a `GO`, freeze pushes** — even a doc tweak voids the verdict. The coordinator assigns the gate seat.
|
||||
- **Query for refutation, never for confirmation.** A subordinate asked to confirm a hypothesis will
|
||||
agree — the bias is in the question, not the answerer, and agent seats are agreeable by construction.
|
||||
State the hypothesis as yours, ask for the evidence that KILLS it, and reproduce when it matters.
|
||||
- **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.
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,262 @@
|
||||
# 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:** EXECUTING (planning complete; RM-01 in flight).
|
||||
**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.
|
||||
|
||||
### First-class principle — observe the property, not the exit code
|
||||
|
||||
> **No write is done until the requested PROPERTY is observed. A success exit code is not evidence.**
|
||||
>
|
||||
> **Success output is designed to be believed.** That is the whole reason the inert-gate class exists
|
||||
> and why P-WRAPPER-001's tri-state (`verified` / `written-unverified` / `failed`) is not optional. The
|
||||
> failure is not carelessness — a green is _engineered_ to be trusted, so trusting it is the default
|
||||
> behaviour of a competent operator, not a lapse.
|
||||
>
|
||||
> Promoted to the charter by Mos (2026-07-31) after the orchestrator committed this exact error: a
|
||||
> `--draft` flag was silently dropped by a wrapper fallback that still exited 0, and the PR was reported
|
||||
> as a draft on the strength of the exit code rather than an observed `draft: true` (D-12). Twelve
|
||||
> failure instances were banked in that session; **three of them were the orchestrator's own.** That
|
||||
> ratio is the point — the mechanism must catch the mechanic too, or it is not a mechanism.
|
||||
>
|
||||
> Operationally: after any write, read back the property you required. Applies to gates, wrappers, PR
|
||||
> flags, commit authorship, file installs, and message delivery alike.
|
||||
|
||||
### First-class principle — pre-registration prevents retrofitting, and nothing else
|
||||
|
||||
> **A pre-registered check set can fail in three distinct ways:**
|
||||
>
|
||||
> | mode | the set is… | found as |
|
||||
> | --------------------------- | ------------------------------------------------- | -------- |
|
||||
> | **WRONG** | a check does not test what it claims | D-8 |
|
||||
> | **INCOMPLETE** | green while a criterion's requirement is untested | D-17 |
|
||||
> | **INTERNALLY INCONSISTENT** | two criteria cannot both hold | D-18 |
|
||||
>
|
||||
> **Pre-registration protects against exactly one thing: retrofitting a check to fit the implementation
|
||||
> it is supposed to judge.** It confers neither correctness, nor coverage, nor consistency. "We
|
||||
> pre-registered the checks" has been treated as though it settled the question — it settles one of
|
||||
> three.
|
||||
>
|
||||
> Promoted to the charter by Mos (2026-07-31). All three modes were found on this mission's own **first
|
||||
> delivery**, by the machinery applied to its own work — not by inspection, and not by looking for them.
|
||||
>
|
||||
> **Enforceable form — RM-02's four clauses.** The registry must establish that: (1) each check is
|
||||
> **right** — proven red for its own stated reason before its green counts; (2) the set **covers** —
|
||||
> every criterion bound to a case that actually exercises it; (3) no two criteria **conflict** —
|
||||
> mutual unsatisfiability is a registry defect discoverable by construction; (4) when a criterion's
|
||||
> meaning changes, the registry **retains original text, restatement, and reason**, so evolution stays
|
||||
> auditable. A criterion with no case that can fail for its own reason is unregistered in substance,
|
||||
> however it reads in the manifest.
|
||||
|
||||
### Corollary — never ship an integrity claim dressed as a property
|
||||
|
||||
> A verification artifact that can be forged by whoever it is meant to catch verifies nothing. If a
|
||||
> manifest, marker, ledger, or receipt is writable by the same actor whose behaviour it certifies, it
|
||||
> **certifies the attack.** Such an artifact must sit inside the integrity envelope it belongs to,
|
||||
> publish atomically, and carry a **tamper negative-control observed red** — otherwise its integrity is
|
||||
> a _claim_, not a _property_.
|
||||
>
|
||||
> **If it cannot be made tamper-evident, say so and reconsider the approach.** Laundering foreign
|
||||
> content as certified is the only unacceptable outcome; an honest "this cannot be verified" is always
|
||||
> available and always preferable.
|
||||
|
||||
### First-class principle — when a property cannot exist at the layer it was specified
|
||||
|
||||
> Some required properties are **impossible at the layer that asked for them** — not hard, impossible.
|
||||
> A local check cannot defend against an actor who can rewrite the check itself. When that happens,
|
||||
> there are exactly three honest moves, and all three are mandatory:
|
||||
>
|
||||
> 1. **Implement what the layer _can_ guarantee.** Partial protection against the class it was actually
|
||||
> born from is worth having.
|
||||
> 2. **State the boundary precisely, in BOTH directions.** What it does _not_ defend, **and** beside it
|
||||
> what it _does_. A reader who sees only the negative dismisses the check as worthless; one who sees
|
||||
> only the positive over-trusts it. **Both together is the honest artifact** — either alone misleads.
|
||||
> 3. **Record where the real guarantee will come from — as a TRACKED DEPENDENCY, not prose.** It must
|
||||
> name a task that someone must close. _A documented gap with no owner becomes a permanent gap that
|
||||
> reads as intentional._
|
||||
>
|
||||
> **A written-down gap is acceptable engineering. An implied-fixed gap is this mission's core failure in
|
||||
> a new costume** — a verification artifact that verifies nothing, with a green to prove it.
|
||||
>
|
||||
> Promoted to the charter by Mos (2026-07-31) from D-19. Origin: the RM-01 symlink manifest could not be
|
||||
> made tamper-evident against a same-UID actor (CWE-345), because the manifest and its marker share one
|
||||
> writable tree. The implementing seat **escalated rather than relabelling self-authentication as
|
||||
> tamper-resistance** — the corollary above firing on its first real adversarial test, on the cheapest
|
||||
> seat in the loop. Residual risk bound to **RM-59** (`depends_on: RM-12, RM-21, RM-25`), where the
|
||||
> choke-point executor and spine verify from _outside_ the worktree's authority.
|
||||
|
||||
### First-class principle — query for refutation, never for confirmation
|
||||
|
||||
> **A subordinate asked to confirm a hypothesis will agree. Ask it to refute, with evidence.**
|
||||
>
|
||||
> The bias is induced by the **query**, not by the answerer's diligence. _"The DB flaked — please
|
||||
> confirm"_ and _"confirm or refute this, with the log line that proves it"_ are different instruments,
|
||||
> and they return different answers to the same question. The first harvests agreement; only the second
|
||||
> can return **"you are wrong, and here is why."**
|
||||
>
|
||||
> This matters most with agent subordinates, which are **agreeable by construction**: fluent, eager to
|
||||
> be useful, and structurally disinclined to tell the dispatcher their premise is false. A confirmation
|
||||
> query aimed at one is close to a guaranteed yes — so the discipline cannot rest on the answerer being
|
||||
> rigorous. **It has to be built into how the question is asked.**
|
||||
>
|
||||
> Promoted to the charter by Mos (2026-08-01). Origin: the orchestrator hypothesised that a coincident
|
||||
> `ci-postgres` failure caused a CI test failure and asked the implementing seat to **confirm or refute**
|
||||
> it. The seat **refuted it** with the log (`ci-postgres:5432 - accepting connections`, migrations
|
||||
> completed) and identified the real cause. Reproduction on an identical head then settled it. Had the
|
||||
> query been phrased for confirmation, the agreement would have been returned, **D-21 would have been
|
||||
> re-classified on a false premise**, and a banked finding would have been silently corrupted.
|
||||
>
|
||||
> **Operationally:** state your hypothesis explicitly, mark it as yours, ask for _evidence that kills
|
||||
> it_, and say what evidence would change your mind. A hypothesis you cannot describe how to falsify is
|
||||
> not yet a hypothesis. Where the answer is consequential, **reproduce** rather than accept — two
|
||||
> independent runs beat one confident report.
|
||||
|
||||
### First-class principle — redundant observation on evidence-bearing steps
|
||||
|
||||
> **Two observers of the same evidence, disagreeing, catch what neither catches alone.** Apply redundancy
|
||||
> not only to judgement calls but to **evidence gathering itself** — the step everyone assumes is
|
||||
> mechanical and therefore skips.
|
||||
>
|
||||
> Promoted by Mos (2026-08-01) from **D-33**. A seat scanned a pipeline with `-f json` and reported 9
|
||||
> steps; the orchestrator scanned the same pipeline in the wrapper's default text mode and reported 8.
|
||||
> **The default output omits `clone`.** Every "full step scan" that night had been 8-of-9 and was stated
|
||||
> as complete in good faith. No verdict changed — but the _method_ was wrong, invisibly, and **only the
|
||||
> disagreement between two counts surfaced it.**
|
||||
>
|
||||
> The reason it survived: **a summary that resembles an enumeration is more dangerous than one that
|
||||
> obviously summarises.** A labelled list of named steps with states _looks_ like the artifact, so nobody
|
||||
> checks it against the record. Compare D-24 — `mergeable` was a _true answer to a different question_;
|
||||
> this was a _true answer to a smaller one_. Neither is a lie; both pass every sniff test.
|
||||
>
|
||||
> **Operationally:** where a step _produces evidence a decision rests on_, have it produced twice by
|
||||
> different means, and treat **any divergence as a finding rather than as noise to reconcile**. Prefer the
|
||||
> machine-readable record over the human-readable rendering — _read the artifact, not the summary_ — and
|
||||
> state the counts observed so a divergence is detectable at all.
|
||||
|
||||
## 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): [`MACP-WIRING-SCOUT.md`](./MACP-WIRING-SCOUT.md) (copied into this dir; TODO discharged). Its findings are sound; its _recommended wire-in point_ is superseded by DECISION-1.
|
||||
|
||||
## 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`** — typed tasks, gate-runner, event ledger, credential binding, tri-state write outcomes. ⚠ **Target CORRECTED 2026-07-31 (DECISION-1, Mos):** a new production Node `TaskExecutor` on the **live** dispatch path (`packages/mosaic` launch + `packages/coord`), which Coord/Forge/live-dispatch submit through. **NOT** `mosaic_orchestrator.py::run_single_task` — that controller is `"enabled": false` and references a dispatcher absent from this checkout; wiring it would strand the executor, reproducing this mission's own disease. The Python rail is **deleted**, not ported. Both planners reached this independently. |
|
||||
| **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. |
|
||||
|
||||
## Why Builds 1–2 are necessary — two independent impossibility proofs
|
||||
|
||||
**The choke-point executor and PG spine are not a design preference. They are forced.** Twice during
|
||||
the mission's own first deliveries, work stopped against a security property that **cannot exist** at
|
||||
the layer that needed it — and both times the only resolution was an authority _outside_ the audited
|
||||
party's control, which is precisely what Builds 1–2 provide.
|
||||
|
||||
| | the audited party controls… | so what fails | found as |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------- | -------- |
|
||||
| **Artifact integrity** | the manifest that certifies its own generated tree (same-UID write ⇒ regenerate manifest _and_ marker consistently, CWE-345) | tamper-evidence | **D-19** |
|
||||
| **Execution integrity** | the code that enters the sandbox (PR-controlled config executes _before_ the boundary exists) | isolation | **D-25** |
|
||||
|
||||
Both reduce to one sentence:
|
||||
|
||||
> **Self-verification by the audited party is not verification.**
|
||||
|
||||
And to one consequence: **the anchor must live outside the audited party's authority.** A local check
|
||||
cannot defend against an actor who can rewrite the check; a repo cannot grant a capability to
|
||||
PR-controlled config and simultaneously prevent that config from using it.
|
||||
|
||||
**An architecture forced by two independent impossibility proofs is stronger evidence than one argued
|
||||
for.** Neither proof was sought — both arrived while trying to ship something else, from different
|
||||
directions (a symlink manifest; a CI sandbox), at different layers. RM-59 and RM-60 are the two tracked
|
||||
dependencies this creates, and they are the same dependency in different clothes.
|
||||
|
||||
## 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.
|
||||
(Per DECISION-1, a P0 phase of provable-gate + activation work precedes this; see `TASKS.md` §3.)
|
||||
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.
|
||||
- ⚠ **QUALIFIED 2026-07-31 (DECISION-2, Mos):** the DB spine **must NOT be a single-point hard-stop.**
|
||||
A broker-independent / degraded mode **and** a rehearsed rollback artifact are **design requirements**
|
||||
(P-RECOVERY-001), binding now on RM-12, RM-13, RM-23, RM-36 and RM-53. This **supersedes** the earlier
|
||||
orchestrator recommendation to pre-commit "no DB ⇒ the fleet stops" — that answer is _not_ on record.
|
||||
Only the specific availability _target_ remains open, queued for Jason; it does **not** block current work.
|
||||
|
||||
## 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 — REFERENCE, do not restate.** The authoritative definitions live at
|
||||
[`~/.config/mosaic/fleet/roles.local/merge-gate.md`](file:///home/hermes/.config/mosaic/fleet/roles.local/merge-gate.md)
|
||||
(verdict authority) and
|
||||
[`~/.config/mosaic/fleet/roles/validator.md`](file:///home/hermes/.config/mosaic/fleet/roles/validator.md)
|
||||
(validator/certificate role). **Read them; do not paraphrase them.** Restating an authoritative source
|
||||
is lossy every time — see D-26, where a subset restated from memory dropped a security precondition.
|
||||
|
||||
**Gate order** (the sequence only; the definitions are in the files above):
|
||||
1. Independent review, **author ≠ reviewer** (`rev-974` on mosaicstack), with PRE-REGISTERED diff-blind
|
||||
acceptance checks committed before the diff is read
|
||||
2. Remediation of findings
|
||||
3. **CI terminal-green** at the exact full-40 head, by **full step scan**
|
||||
4. **Merge-gate verdict — `GO` / `NO-GO` / `HOLD`** (class `merge-gate`; "Ultron" is an _instance name_,
|
||||
display data, never an authority source). **Bound to a commit and VOID the instant the head moves.**
|
||||
`HOLD` persists until replaced; a `NO-GO` answered by an empty commit must be re-issued as `NO-GO`.
|
||||
Posted durably on the PR under the gate's own minted identity, **enumerating** its evidence — a bare
|
||||
"GO — gates verified" is non-conforming.
|
||||
5. **Coordinator merge**, head-pinned. The gate never merges; it holds `push=False` by design.
|
||||
|
||||
⚠ **The CI queue guard runs but is ZERO-INFORMATION until RM-03 lands** (D-23: it returns pass for every
|
||||
possible input). It must not be cited as evidence by any gate, including the coordinator's own merge path.
|
||||
|
||||
**Assignment:** the coordinator assigns the merge-gate seat; the orchestrator does not. The orchestrator
|
||||
owns getting a PR _gate-ready_.
|
||||
|
||||
- **Compaction survival:** see `KICKSTART.md` in this dir — the resume procedure. Persist typed state, not transcript.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -109,6 +109,55 @@ else
|
||||
detect_platform >/dev/null
|
||||
fi
|
||||
|
||||
# Render the provider's own explanation for a failed request, for appending to
|
||||
# an error message (#1004). Every HTTP arm in this file already has the response
|
||||
# body on disk; without this it was discarded unread at exactly the moment the
|
||||
# caller needed it, which pushes an operator toward re-issuing the request by
|
||||
# hand to find out what the server said. Gitea returns {"message": "..."} on a
|
||||
# refusal; anything unparseable falls back to a truncated raw first line so a
|
||||
# proxy's HTML error page still says something. Prints "" when there is nothing
|
||||
# to add, so callers can interpolate unconditionally.
|
||||
#
|
||||
# Args: $1 = path to the response body file.
|
||||
gitea_error_detail() {
|
||||
local body_file="$1"
|
||||
[[ -s "$body_file" ]] || return 0
|
||||
python3 - "$body_file" <<'PY' 2>/dev/null || true
|
||||
import json
|
||||
import sys
|
||||
|
||||
LIMIT = 300
|
||||
try:
|
||||
with open(sys.argv[1], encoding="utf-8", errors="replace") as response:
|
||||
raw = response.read().strip()
|
||||
except OSError:
|
||||
raise SystemExit(0)
|
||||
if not raw:
|
||||
raise SystemExit(0)
|
||||
detail = ""
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
if isinstance(parsed, dict):
|
||||
for key in ("message", "error", "errors"):
|
||||
value = parsed.get(key)
|
||||
if isinstance(value, str) and value.strip():
|
||||
detail = value.strip()
|
||||
break
|
||||
if isinstance(value, list) and value:
|
||||
detail = "; ".join(str(item) for item in value).strip()
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
if not detail:
|
||||
detail = raw.splitlines()[0].strip()
|
||||
if not detail:
|
||||
raise SystemExit(0)
|
||||
if len(detail) > LIMIT:
|
||||
detail = detail[:LIMIT] + "..."
|
||||
print(f" — provider said: {detail}")
|
||||
PY
|
||||
}
|
||||
|
||||
# Post a comment to a Gitea PR (PR comments ARE issue comments) via the
|
||||
# supported REST API and verify it against a PROVIDER-RETURNED created id. The
|
||||
# write is a direct POST that returns the created comment object, so we learn
|
||||
@@ -150,7 +199,7 @@ print(json.dumps({"body": os.environ["COMMENT_BODY"]}))
|
||||
return 1
|
||||
fi
|
||||
if [[ "$write_status" != "201" ]]; then
|
||||
echo "Error: Gitea comment write failed with HTTP $write_status" >&2
|
||||
echo "Error: Gitea comment write failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -179,7 +228,7 @@ PY
|
||||
return 1
|
||||
fi
|
||||
if [[ "$readback_status" != "200" ]]; then
|
||||
echo "Error: Gitea comment read-back failed with HTTP $readback_status" >&2
|
||||
echo "Error: Gitea comment read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -370,7 +419,7 @@ gitea_authenticated_login() {
|
||||
return 1
|
||||
fi
|
||||
if [[ "$status" != "200" ]]; then
|
||||
echo "Error: Gitea authenticated-identity read failed with HTTP $status" >&2
|
||||
echo "Error: Gitea authenticated-identity read failed with HTTP $status$(gitea_error_detail "$response_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -407,7 +456,7 @@ gitea_read_pr_head_into() {
|
||||
return 1
|
||||
fi
|
||||
if [[ "$status" != "200" ]]; then
|
||||
echo "Error: Gitea PR head read failed with HTTP $status" >&2
|
||||
echo "Error: Gitea PR head read failed with HTTP $status$(gitea_error_detail "$pr_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
python3 - "$pr_file" <<'PY'
|
||||
@@ -497,7 +546,7 @@ print(json.dumps({
|
||||
fi
|
||||
# Gitea returns 200 (occasionally 201) with the created review object.
|
||||
if [[ "$write_status" != "200" && "$write_status" != "201" ]]; then
|
||||
echo "Error: Gitea review submit failed with HTTP $write_status (#865: no durable review created)" >&2
|
||||
echo "Error: Gitea review submit failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -526,7 +575,7 @@ PY
|
||||
return 1
|
||||
fi
|
||||
if [[ "$readback_status" != "200" ]]; then
|
||||
echo "Error: Gitea review read-back failed with HTTP $readback_status" >&2
|
||||
echo "Error: Gitea review read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ CREDENTIALS_FILE="$WORK_DIR/credentials.json"
|
||||
# A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak
|
||||
# check can assert every POST/GET body + metadata temp file is cleaned up.
|
||||
TMP_SCRATCH="$WORK_DIR/scratch"
|
||||
# Sandboxed HOME so nothing under the real $HOME (notably the per-slot Gitea token
|
||||
# store at ~/.config/mosaic/secrets/gitea-tokens/) is reachable from the wrapper.
|
||||
HOME_DIR="$WORK_DIR/home"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
@@ -78,9 +81,18 @@ OVERRIDE_TOKEN="override-token-placeholder"
|
||||
CROSS_HOST_LOGIN="foreign-host-reviewer"
|
||||
CROSS_HOST_TOKEN="cross-host-token-placeholder"
|
||||
|
||||
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH"
|
||||
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH" "$HOME_DIR"
|
||||
git -C "$REPO_DIR" init -q
|
||||
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
||||
# HERMETICITY: get_gitea_token() step 0 resolves a per-agent identity from
|
||||
# `git config --get mosaic.gitIdentity`, which on a provisioned agent seat is set
|
||||
# GLOBALLY and therefore leaks into this fresh repo. It then reads a REAL per-slot
|
||||
# token from $HOME and returns it WITHOUT ever consulting MOSAIC_CREDENTIALS_FILE,
|
||||
# so the fixture credentials below are silently ignored and the suite runs against
|
||||
# production credentials. An empty repo-local value shadows the global one and reads
|
||||
# back as empty at rc=0, restoring the shared-credential path this suite intends to
|
||||
# exercise. Paired with the sandboxed HOME in run_review().
|
||||
git -C "$REPO_DIR" config mosaic.gitIdentity ""
|
||||
|
||||
# tea config: the override login carries its own token here. The default login
|
||||
# name ("mosaicstack") is deliberately absent, so the no-override default path
|
||||
@@ -266,6 +278,24 @@ submitted = json.loads(os.environ["PR_REVIEW_PAYLOAD"])
|
||||
with open(state_path, encoding="utf-8") as handle:
|
||||
reviews = json.load(handle)
|
||||
|
||||
# review-refused-422 (#1004): the server REFUSES the submit outright with a
|
||||
# definite, correct, machine-readable reason in the body — the shape Gitea
|
||||
# returns when the acting credential authored the PR. Nothing is created. The
|
||||
# wrapper must surface what the server said and must NOT relabel this as the
|
||||
# #865 silent-no-op defect class, which is precisely what it is not.
|
||||
if mode == "review-refused-422":
|
||||
print("422")
|
||||
print(json.dumps({"message": "Cannot approve your own pull request"}))
|
||||
raise SystemExit(0)
|
||||
|
||||
# review-refused-html (#1004): a non-JSON error body, as a fronting proxy or
|
||||
# gateway emits. The detail extraction must degrade to the first raw line rather
|
||||
# than silently dropping the only explanation available.
|
||||
if mode == "review-refused-html":
|
||||
print("502")
|
||||
print("<html><head><title>502 Bad Gateway</title></head>\n<body>nginx</body></html>")
|
||||
raise SystemExit(0)
|
||||
|
||||
# no-op-concurrent-review: the wrapper's own submit is SUPPRESSED (200, no
|
||||
# created object) even though a concurrent same-identity, same-state review at
|
||||
# the same head already exists. Nothing is persisted; no created id to verify.
|
||||
@@ -517,6 +547,8 @@ run_review() {
|
||||
cd "$REPO_DIR"
|
||||
PATH="$BIN_DIR:$PATH" \
|
||||
TMPDIR="$TMP_SCRATCH" \
|
||||
HOME="$HOME_DIR" \
|
||||
MOSAIC_GIT_IDENTITY="" \
|
||||
XDG_CONFIG_HOME="$XDG_DIR" \
|
||||
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
|
||||
PR_REVIEW_TEA_LOG="$TEA_LOG" \
|
||||
@@ -940,4 +972,44 @@ if grep -q 'Approved and verified' "$OUTPUT_FILE"; then
|
||||
fi
|
||||
assert_no_temp_leak "review-body-null"
|
||||
|
||||
# Case 19 (#1004): an outright server REFUSAL must report the provider's own
|
||||
# reason and must NOT be relabelled as the #865 silent-no-op defect class. The
|
||||
# old arm hardcoded "(#865: no durable review created)" for EVERY non-2xx, so a
|
||||
# 422/403/404 — all of them definite, correct refusals the server explained in
|
||||
# the discarded body — arrived at the caller wearing the name of the one defect
|
||||
# they are not. That misdirection is what makes an operator re-issue the request
|
||||
# by hand against the live object to find out what actually happened.
|
||||
if run_review review-refused-422 approve; then
|
||||
echo "FAIL: approve reported success when the server refused the submit" >&2
|
||||
cat "$OUTPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'HTTP 422' "$OUTPUT_FILE"
|
||||
if ! grep -q 'Cannot approve your own pull request' "$OUTPUT_FILE"; then
|
||||
echo "FAIL: the provider's stated reason was discarded (#1004)" >&2
|
||||
cat "$OUTPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q '#865: no durable review created' "$OUTPUT_FILE"; then
|
||||
echo "FAIL: a server refusal was misattributed to the #865 defect class (#1004)" >&2
|
||||
cat "$OUTPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_no_temp_leak "review-refused-422"
|
||||
|
||||
# Case 20 (#1004): a non-JSON error body (a fronting proxy's HTML page) must
|
||||
# still yield something the caller can act on, rather than a bare status code.
|
||||
if run_review review-refused-html approve; then
|
||||
echo "FAIL: approve reported success on a 502" >&2
|
||||
cat "$OUTPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'HTTP 502' "$OUTPUT_FILE"
|
||||
if ! grep -q '502 Bad Gateway' "$OUTPUT_FILE"; then
|
||||
echo "FAIL: a non-JSON error body was dropped instead of degrading to its first line (#1004)" >&2
|
||||
cat "$OUTPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_no_temp_leak "review-refused-html"
|
||||
|
||||
echo "pr-review.sh REST review + comment create/read-back regression passed"
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-test-enumeration.sh — CI test-membership guard (#1017).
|
||||
#
|
||||
# CI reaches shell suites through two hand-enumerated surfaces:
|
||||
# S1 packages/mosaic/package.json scripts."test:framework-shell"
|
||||
# S2 .woodpecker/ci.yml direct `bash packages/mosaic/framework/tools/...` commands
|
||||
#
|
||||
# A hand-enumerated allowlist re-arms its own gap: a new suite never auto-joins,
|
||||
# so the list silently under-runs the disk (17 of 39 suites were invisible when
|
||||
# #1017 was filed). This guard makes that under-run impossible to do silently:
|
||||
#
|
||||
# FAIL when a suite-shaped file exists on disk and is neither enumerated on
|
||||
# the UNION of both surfaces nor listed in the exclusions file.
|
||||
# ("Enumerated", deliberately — F1/F2 on PR #1018 proved this guard sees
|
||||
# NAMING, not reachability, and its words must not claim otherwise.)
|
||||
# FAIL when either surface names a path that does not exist on disk
|
||||
# (a rename manufactures a stale entry silently — checked BOTH directions).
|
||||
# FAIL when an exclusion entry has no reason, names a path that is gone,
|
||||
# names a path that is also enumerated (contradiction), or names a path
|
||||
# outside the population (dead weight that looks like coverage).
|
||||
#
|
||||
# POPULATION PATTERN — a deliberate decision, stated per #1017's record:
|
||||
# basename matches *test*.sh (contains "test", ends ".sh"). Deliberately BROAD:
|
||||
# the strict `test-*.sh` prefix cannot even name three real boundary files
|
||||
# (tmux/agent-send.test.sh — CI-run; orchestrator/smoke-test.sh;
|
||||
# wake/validate-973/microtest-wake-assert.sh), and three independent censuses
|
||||
# handled that last file three different ways with no trace of the judgement.
|
||||
# The broad pattern makes such files MEMBERS, so their disposition must be a
|
||||
# signed exclusion, not an accident of the glob. The SAME pattern is applied to
|
||||
# both sides of the comparison (disk and enumeration) — a comparison globbed two
|
||||
# ways runs on two different populations. Scripts outside the pattern on both
|
||||
# sides symmetrically (e.g. check-resident-budget.sh, verify-sanitized.sh) are
|
||||
# check-scripts, not suites; their existence is still verified via the
|
||||
# both-directions rule because every surface-named path must exist on disk.
|
||||
#
|
||||
# The surfaces are PARSED, never line-ranged: three seats independently
|
||||
# mis-scoped hand-written line ranges against these files (#1017 thread). S1 is
|
||||
# read via JSON + command-chain tokenization; S2 by extracting every
|
||||
# packages/mosaic/framework/tools/ token wherever it appears in the file.
|
||||
#
|
||||
# Exclusions file format (framework/tools/quality/test-enumeration-exclusions.txt):
|
||||
# <repo-relative-path> | <non-empty reason>
|
||||
# Lines starting with # and blank lines are ignored. An exclusion is a recorded
|
||||
# decision someone signed, not an omission nobody made.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)"
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--root) ROOT="$(cd "$2" && pwd)"; shift 2 ;;
|
||||
*) echo "usage: check-test-enumeration.sh [--root <repo-root>]" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
PKG_JSON="$ROOT/packages/mosaic/package.json"
|
||||
CI_YML="$ROOT/.woodpecker/ci.yml"
|
||||
TOOLS_DIR="$ROOT/packages/mosaic/framework/tools"
|
||||
EXCLUSIONS="$TOOLS_DIR/quality/test-enumeration-exclusions.txt"
|
||||
|
||||
for f in "$PKG_JSON" "$CI_YML"; do
|
||||
[[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; }
|
||||
done
|
||||
[[ -d "$TOOLS_DIR" ]] || { echo "FAIL: tools dir missing: $TOOLS_DIR" >&2; exit 2; }
|
||||
|
||||
fail_count=0
|
||||
fail() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); }
|
||||
|
||||
# in_population <repo-relative path> — the single pattern, used for BOTH sides.
|
||||
in_population() {
|
||||
local base; base="$(basename "$1")"
|
||||
[[ "$base" == *test*.sh ]]
|
||||
}
|
||||
|
||||
# --- Surface 1: package.json test:framework-shell, parsed, repo-relative -----
|
||||
# Tokens are script paths iff they contain "/" and end .sh/.py; interpreter
|
||||
# names and flags are skipped. Paths are relative to packages/mosaic/.
|
||||
mapfile -t S1 < <(python3 - "$PKG_JSON" <<'PY'
|
||||
import json, shlex, sys
|
||||
cmd = json.load(open(sys.argv[1]))["scripts"].get("test:framework-shell", "")
|
||||
seen = []
|
||||
for seg in cmd.split("&&"):
|
||||
for tok in shlex.split(seg):
|
||||
if "/" in tok and (tok.endswith(".sh") or tok.endswith(".py")):
|
||||
path = "packages/mosaic/" + tok
|
||||
if path not in seen:
|
||||
seen.append(path)
|
||||
print("\n".join(seen))
|
||||
PY
|
||||
)
|
||||
|
||||
# --- Surface 2: ci.yml, every framework/tools token wherever it appears ------
|
||||
# Comment lines (first non-whitespace char is #) are skipped BEFORE matching:
|
||||
# commenting an invocation out is the most common way a suite actually gets
|
||||
# disabled, and a raw-text regex would keep calling it enumerated (F1, 20155 on
|
||||
# PR #1018 — demonstrated, not argued). Known residual limit: a path named only
|
||||
# in a TRAILING comment on a live line still matches; no such line exists today
|
||||
# and full fidelity would need a YAML parser the CI image does not ship.
|
||||
mapfile -t S2 < <(grep -vE '^[[:space:]]*#' "$CI_YML" \
|
||||
| grep -oE 'packages/mosaic/framework/tools/[A-Za-z0-9_./-]+\.(sh|py)' | sort -u)
|
||||
|
||||
# --- Union, and its population-restricted view -------------------------------
|
||||
declare -A ENUM=() ENUM_POP=()
|
||||
for p in "${S1[@]:-}" "${S2[@]:-}"; do
|
||||
[[ -n "$p" ]] || continue
|
||||
ENUM["$p"]=1
|
||||
in_population "$p" && ENUM_POP["$p"]=1
|
||||
done
|
||||
|
||||
# --- Direction B: every surface-named path must exist on disk ----------------
|
||||
for p in "${!ENUM[@]}"; do
|
||||
[[ -f "$ROOT/$p" ]] || fail "STALE ENUMERATION: surfaces name '$p' but it does not exist on disk"
|
||||
done
|
||||
|
||||
# --- Exclusions: parsed with the same rigor the enumeration gets -------------
|
||||
declare -A EXCLUDED=()
|
||||
if [[ -f "$EXCLUSIONS" ]]; then
|
||||
lineno=0
|
||||
while IFS= read -r line; do
|
||||
lineno=$(( lineno + 1 ))
|
||||
[[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue
|
||||
path="${line%%|*}"; reason="${line#*|}"
|
||||
path="$(echo "$path" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
reason="$(echo "$reason" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
if [[ "$line" != *"|"* || -z "$reason" ]]; then
|
||||
fail "EXCLUSION MISSING REASON: line $lineno ('$path') — an exclusion is a recorded decision someone signed"
|
||||
continue
|
||||
fi
|
||||
if [[ ! -f "$ROOT/$path" ]]; then
|
||||
fail "STALE EXCLUSION: line $lineno excludes '$path' which does not exist on disk"
|
||||
continue
|
||||
fi
|
||||
if ! in_population "$path"; then
|
||||
fail "EXCLUSION OUTSIDE POPULATION: line $lineno excludes '$path' which the population pattern does not name — dead weight that reads as coverage"
|
||||
continue
|
||||
fi
|
||||
if [[ -n "${ENUM[$path]:-}" ]]; then
|
||||
fail "CONTRADICTORY EXCLUSION: line $lineno excludes '$path' which the surfaces already enumerate"
|
||||
continue
|
||||
fi
|
||||
EXCLUDED["$path"]=1
|
||||
done < "$EXCLUSIONS"
|
||||
fi
|
||||
|
||||
# --- Direction A: disk population must be enumerated or signed-excluded ------
|
||||
disk_total=0
|
||||
unlisted=0
|
||||
while IFS= read -r f; do
|
||||
rel="${f#"$ROOT"/}"
|
||||
in_population "$rel" || continue
|
||||
disk_total=$(( disk_total + 1 ))
|
||||
if [[ -z "${ENUM_POP[$rel]:-}" && -z "${EXCLUDED[$rel]:-}" ]]; then
|
||||
fail "UNENUMERATED: '$rel' exists on disk but is neither enumerated on any CI surface nor signed in the exclusions file"
|
||||
unlisted=$(( unlisted + 1 ))
|
||||
fi
|
||||
done < <(find "$TOOLS_DIR" -type f -name '*.sh' | sort)
|
||||
|
||||
if (( fail_count > 0 )); then
|
||||
printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \
|
||||
"$fail_count" "$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}"
|
||||
exit 1
|
||||
fi
|
||||
printf 'enumeration guard: OK — population %d, enumerated (in-population) %d, excluded (signed) %d, surfaces name %d path(s), all present on disk\n' \
|
||||
"$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}" "${#ENUM[@]}"
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-check-test-enumeration.sh — needles for the enumeration guard (#1017).
|
||||
#
|
||||
# Every failure mode the guard promises gets BOTH polarities:
|
||||
# NEEDLE a fixture that MUST trip the guard, asserted on the guard's OWN
|
||||
# words (--out) — exit 1 alone cannot distinguish "caught the rogue
|
||||
# file" from "choked on the fixture".
|
||||
# CONTROL a fixture that MUST pass. A guard that failed unconditionally
|
||||
# would satisfy every needle here — the null-case defect the guard's
|
||||
# own subject matter (#1017) exists to make impossible.
|
||||
#
|
||||
# The needles encode the specific errors that produced #1017's thread:
|
||||
# n6 is the 20124 boundary file (a suite the strict prefix cannot name);
|
||||
# n2b proves surface 2 is PARSED, not line-ranged (three seats mis-scoped
|
||||
# hand-written ranges against ci.yml);
|
||||
# n5/n7 keep the exclusions file honest so it cannot become the next silent cap;
|
||||
# n8/c4 are F1 (20155): a commented-out ci.yml line is NOT enumeration —
|
||||
# commenting-out is the most common way a suite actually gets disabled,
|
||||
# and it must fail loud in one direction without false-staling the other.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
GUARD="$HERE/check-test-enumeration.sh"
|
||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||
PASS=0; FAIL=0
|
||||
|
||||
# fixture <name> — a minimal repo root the guard accepts via --root:
|
||||
# one suite enumerated on S1 (plus a naming-outlier suite, so the S1 parser's
|
||||
# handling of non-prefix names is always exercised), one on S2, one check-script
|
||||
# named on S2 that is outside the population, and an empty exclusions file.
|
||||
fixture() {
|
||||
local r="$TMP/$1"
|
||||
mkdir -p "$r/packages/mosaic/framework/tools/git" \
|
||||
"$r/packages/mosaic/framework/tools/tmux" \
|
||||
"$r/packages/mosaic/framework/tools/quality/scripts" \
|
||||
"$r/.woodpecker"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/git/test-a.sh"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/tmux/outlier.test.sh"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/verify-thing.sh"
|
||||
cat > "$r/packages/mosaic/package.json" <<'JSON'
|
||||
{"scripts": {"test:framework-shell": "bash framework/tools/git/test-a.sh && bash framework/tools/tmux/outlier.test.sh"}}
|
||||
JSON
|
||||
cat > "$r/.woodpecker/ci.yml" <<'YML'
|
||||
steps:
|
||||
sanitize:
|
||||
commands:
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/verify-thing.sh
|
||||
guard:
|
||||
commands:
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/test-ci.sh
|
||||
YML
|
||||
: > "$r/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"
|
||||
printf '%s' "$r"
|
||||
}
|
||||
|
||||
# expect <kind> <want-exit> <desc> [--out <substring>] -- <root>
|
||||
expect() {
|
||||
local kind="$1" want="$2" desc="$3"; shift 3
|
||||
local need_out=""
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--out) need_out="$2"; shift 2 ;;
|
||||
--) shift; break ;;
|
||||
esac
|
||||
done
|
||||
local root="$1" got=0 out
|
||||
out="$(bash "$GUARD" --root "$root" 2>&1)" || got=$?
|
||||
local why=""
|
||||
[[ "$got" == "$want" ]] || why="wanted exit $want, got $got"
|
||||
if [[ -z "$why" && -n "$need_out" && "$out" != *"$need_out"* ]]; then
|
||||
why="exit $got as expected, but output never said: $need_out"
|
||||
fi
|
||||
if [[ -z "$why" ]]; then
|
||||
printf ' PASS [%-7s] %s (exit %s)\n' "$kind" "$desc" "$got"
|
||||
PASS=$(( PASS + 1 ))
|
||||
else
|
||||
printf ' FAIL [%-7s] %s — %s\n' "$kind" "$desc" "$why"
|
||||
printf '%s\n' "$out" | sed 's/^/ | /'
|
||||
FAIL=$(( FAIL + 1 ))
|
||||
fi
|
||||
}
|
||||
|
||||
excl() { printf '%s\n' "$2" >> "$1/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"; }
|
||||
|
||||
echo "=== c1: a fully consistent fixture passes ==="
|
||||
R="$(fixture c1)"
|
||||
expect CONTROL 0 "consistent tree: both surfaces enumerated, nothing unlisted" \
|
||||
--out "enumeration guard: OK" -- "$R"
|
||||
|
||||
echo "=== n1: an on-disk suite reachable from no surface must fail ==="
|
||||
R="$(fixture n1)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
||||
expect NEEDLE 1 "unlisted suite is named in the failure" \
|
||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-rogue.sh'" -- "$R"
|
||||
|
||||
echo "=== n6: the 20124 boundary file — a suite the strict prefix cannot name ==="
|
||||
R="$(fixture n6)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/rogue.test.sh"
|
||||
expect NEEDLE 1 "naming-outlier suite (*.test.sh) is a population member, not invisible" \
|
||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/rogue.test.sh'" -- "$R"
|
||||
|
||||
echo "=== c3: a non-suite script outside the pattern is outside it on BOTH sides ==="
|
||||
R="$(fixture c3)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/check-unrelated.sh"
|
||||
expect CONTROL 0 "check-script on disk, unlisted, outside population: not the guard's business" \
|
||||
--out "enumeration guard: OK" -- "$R"
|
||||
|
||||
echo "=== n2/n2b: a surface naming a path absent from disk must fail — both surfaces ==="
|
||||
R="$(fixture n2)"
|
||||
rm "$R/packages/mosaic/framework/tools/git/test-a.sh"
|
||||
expect NEEDLE 1 "S1 (package.json) stale entry" \
|
||||
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/git/test-a.sh'" -- "$R"
|
||||
R="$(fixture n2b)"
|
||||
rm "$R/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
|
||||
expect NEEDLE 1 "S2 (ci.yml) stale entry — proves ci.yml is parsed, not line-ranged" \
|
||||
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/quality/scripts/test-ci.sh'" -- "$R"
|
||||
|
||||
echo "=== c2: a rogue suite with a SIGNED exclusion passes, and is counted ==="
|
||||
R="$(fixture c2)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | non-hermetic pending fixture work (needle-suite specimen)"
|
||||
expect CONTROL 0 "signed exclusion is honoured and visible in the summary" \
|
||||
--out "excluded (signed) 1" -- "$R"
|
||||
|
||||
echo "=== n3: an exclusion with no reason is not a decision ==="
|
||||
R="$(fixture n3)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | "
|
||||
expect NEEDLE 1 "empty reason rejected" --out "EXCLUSION MISSING REASON" -- "$R"
|
||||
R="$(fixture n3b)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh"
|
||||
expect NEEDLE 1 "missing separator rejected (the path alone is not a signature)" \
|
||||
--out "EXCLUSION MISSING REASON" -- "$R"
|
||||
|
||||
echo "=== n4: an exclusion whose path is gone is stale, not satisfied ==="
|
||||
R="$(fixture n4)"
|
||||
excl "$R" "packages/mosaic/framework/tools/git/test-vanished.sh | was excluded once, then deleted"
|
||||
expect NEEDLE 1 "stale exclusion rejected" --out "STALE EXCLUSION" -- "$R"
|
||||
|
||||
echo "=== n5: excluding an enumerated suite is a contradiction, not belt-and-braces ==="
|
||||
R="$(fixture n5)"
|
||||
excl "$R" "packages/mosaic/framework/tools/git/test-a.sh | already in CI but excluded anyway"
|
||||
expect NEEDLE 1 "contradictory exclusion rejected" --out "CONTRADICTORY EXCLUSION" -- "$R"
|
||||
|
||||
echo "=== n8/c4: a commented-out ci.yml line is not enumeration (F1, 20155) ==="
|
||||
R="$(fixture n8)"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-disabled.sh"
|
||||
printf ' # - bash packages/mosaic/framework/tools/git/test-disabled.sh\n' >> "$R/.woodpecker/ci.yml"
|
||||
expect NEEDLE 1 "suite named only in a commented-out invocation is UNENUMERATED" \
|
||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-disabled.sh'" -- "$R"
|
||||
R="$(fixture c4)"
|
||||
printf ' # - bash packages/mosaic/framework/tools/git/test-vanished.sh\n' >> "$R/.woodpecker/ci.yml"
|
||||
expect CONTROL 0 "comment naming an absent path raises no false stale-enumeration" \
|
||||
--out "enumeration guard: OK" -- "$R"
|
||||
|
||||
echo "=== n7: excluding a file outside the population is dead weight, not coverage ==="
|
||||
R="$(fixture n7)"
|
||||
excl "$R" "packages/mosaic/framework/tools/quality/scripts/verify-thing.sh | not a suite but signing it anyway"
|
||||
expect NEEDLE 1 "out-of-population exclusion rejected" --out "EXCLUSION OUTSIDE POPULATION" -- "$R"
|
||||
|
||||
echo
|
||||
printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL"
|
||||
(( FAIL == 0 ))
|
||||
@@ -0,0 +1,45 @@
|
||||
# test-enumeration-exclusions.txt — signed exclusions for check-test-enumeration.sh (#1017).
|
||||
#
|
||||
# Every entry is a recorded decision: a suite-shaped file that exists on disk,
|
||||
# is NOT reachable from any CI surface, and carries the reason someone signed
|
||||
# for that. The guard FAILS on an entry with no reason, a stale path, or a path
|
||||
# the surfaces already enumerate. Burning an entry down = making it CI-reachable
|
||||
# (package.json test:framework-shell or a ci.yml step) and deleting its line.
|
||||
#
|
||||
# Format: <repo-relative path> | <reason>
|
||||
# All entries below were signed at #1017's filing base (main 826a8b3b, 2026-07-31)
|
||||
# by pepper (sb-it-1-dt); measurements cited are one-run assertions from that seat.
|
||||
|
||||
# --- tools/git: the #1007 five — non-hermetic, resolve real credentials ---
|
||||
packages/mosaic/framework/tools/git/test-pr-merge-gitea-empty-uid.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix (git -C scoping)
|
||||
packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
|
||||
packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
|
||||
packages/mosaic/framework/tools/git/test-pr-metadata-gitea.sh | resolves real credentials (#1007 census, fourth entry via family-grep); joins CI after the wrapper-half hermeticity fix
|
||||
packages/mosaic/framework/tools/git/test-issue-comment-readback.sh | resolves real credentials (#1007 census, fifth entry); joins CI after the wrapper-half hermeticity fix
|
||||
|
||||
# --- tools/git: push guards — measured green locally, CI-image fitness unverified ---
|
||||
packages/mosaic/framework/tools/git/test-push-guard.sh | measured green at 826a8b3b (46 passed / 0 failed, one run, 2026-07-31); CI-image fitness unverified; #1017 burndown
|
||||
packages/mosaic/framework/tools/git/test-mutate-push-guard.sh | measured green at 826a8b3b (8/0, 13 mutants killed 0 survived, one run, 2026-07-31); requires setsid (util-linux), absent from the alpine base image; #1017 burndown
|
||||
packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh | hermeticity unaudited — the unprotected suite in #1007's protected/unprotected split; audit before CI; #1017 burndown
|
||||
|
||||
# --- tools/git: unmeasured ---
|
||||
packages/mosaic/framework/tools/git/test-verify-clean-clone.sh | unmeasured in CI image; asserts git file-mode (100644/755) semantics that need verification on the CI filesystem first; #1017 burndown
|
||||
packages/mosaic/framework/tools/git/test-help-exit-code.sh | unmeasured in CI image; stub-based (#701 regression harness), likely CI-fit; #1017 burndown
|
||||
packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh | unmeasured in CI image; fixture-based (#546/#547 regression harness), likely CI-fit; #1017 burndown
|
||||
|
||||
# --- tools/tmux: require a live tmux server ---
|
||||
packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion)
|
||||
packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling)
|
||||
|
||||
# --- single-suite directories: unmeasured in CI ---
|
||||
packages/mosaic/framework/tools/fleet/test-start-agent-session.sh | unmeasured in CI image; stubs tmux via a fake bin dir, likely CI-fit; #1017 burndown
|
||||
packages/mosaic/framework/tools/glpi/test-list-http-status.sh | unmeasured in CI image; stub-based (#807 regression harness), likely CI-fit; #1017 burndown
|
||||
packages/mosaic/framework/tools/orchestrator/test-board-roll.sh | unmeasured in CI image; file-fixture based, likely CI-fit; #1017 burndown
|
||||
packages/mosaic/framework/tools/woodpecker/test-ci-wait-exit-matrix.sh | unmeasured in CI image; drives ci-wait.sh against a stub pipeline-status.sh, likely CI-fit; #1017 burndown
|
||||
|
||||
# --- naming-boundary files the strict test-*.sh prefix cannot even name ---
|
||||
# (#1017: three independent censuses handled the microtest file three different
|
||||
# ways — editorial drop, structural exclusion, accidental inclusion — with no
|
||||
# recorded judgement. These lines ARE that judgement, signed.)
|
||||
packages/mosaic/framework/tools/orchestrator/smoke-test.sh | behavior smoke checks for coord continue/run workflows, run manually by orchestrator seats; unmeasured in CI; #1017 burndown
|
||||
packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh | #973 instrument self-test, run as a precondition of the validate-973 evidence procedure rather than as a standing CI suite; #1017 burndown candidate
|
||||
@@ -529,7 +529,19 @@ cmd_run() {
|
||||
echo "detector.sh: WARN — off-host liveness beacon emit failed (see beacon.sh); the off-host absence check remains the authoritative dead-man." >&2
|
||||
fi
|
||||
[ "$once" -eq 1 ] && break
|
||||
sleep "$interval"
|
||||
# Close the detector lock fd in the sleep child; otherwise an orphaned sleep
|
||||
# keeps the single-instance flock (fd 9, taken at exec 9> above) alive after
|
||||
# the detector parent dies. The lock is non-blocking (`flock -n`, above), so
|
||||
# for as long as that sleep survives a replacement instance is REFUSED and
|
||||
# exits rather than queueing. This particular hold is BOUNDED by one poll
|
||||
# interval (WAKE_DETECTOR_INTERVAL, default 30s): when the orphaned sleep
|
||||
# exits its copy of fd 9 closes, ending this bounded sleep-child hold. It
|
||||
# does NOT follow that the next start succeeds — other inheritors of fd 9
|
||||
# (the M1 adapter, M2 sink grandchildren) are outside this patch's scope and
|
||||
# can keep holding the flock. The cost this removes is a restart window in
|
||||
# which every supervisor retry fails on the sleep child's account.
|
||||
# `9>&-` closes ONLY the child's copy — the parent's lock is unaffected.
|
||||
sleep "$interval" 9>&-
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
|
||||
@@ -31,7 +31,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||
SIGN="$SCRIPT_DIR/sign.sh"
|
||||
|
||||
@@ -119,7 +119,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
|
||||
@@ -27,7 +27,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
ORACLE="$SCRIPT_DIR/fn-oracle.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
WI="$SCRIPT_DIR/wake-install.sh"
|
||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
@@ -48,7 +48,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
PRE="$SCRIPT_DIR/preimage.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
|
||||
@@ -31,7 +31,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
RECON="$SCRIPT_DIR/reconcile.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
|
||||
@@ -44,7 +44,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
ACK="$SCRIPT_DIR/ack.sh"
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ set -uo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
||||
exit 97
|
||||
fi
|
||||
wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
|
||||
@@ -10,8 +10,9 @@ comparison come from independent code paths.
|
||||
Subcommands (all print sorted, stable output; non-zero exit on any failure):
|
||||
|
||||
expected The expected coordinate set from the ARTIFACT: one
|
||||
"<helper> <file>:<line+3>" row per denominator row (+3 = the
|
||||
uniform header shift the converter applied; converter-verified).
|
||||
"<helper> <file>:<line+7>" row per denominator row (+7 = 3
|
||||
converter header lines + 4 lines from the #984 source guard,
|
||||
uniform across all ten suites).
|
||||
Multi-grep lines stay ONE coordinate.
|
||||
|
||||
static The converted-site inventory from the SOURCE TEXT at the current
|
||||
@@ -23,9 +24,9 @@ Subcommands (all print sorted, stable output; non-zero exit on any failure):
|
||||
from the text.)
|
||||
|
||||
arms The forced-error arm list: the 19 denominator canaries plus one
|
||||
E-form arm (store-ack:733→736, a $(count_lines) capture compared
|
||||
afterward — the A6 shape) plus one F-form arm (quarantine:560→563,
|
||||
the multi-grep pipeline capture), as "<helper> <file>:<line+3>
|
||||
E-form arm (store-ack:733→740, a $(count_lines) capture compared
|
||||
afterward — the A6 shape) plus one F-form arm (quarantine:560→567,
|
||||
the multi-grep pipeline capture), as "<helper> <file>:<line+7>
|
||||
<form>". Both extras are asserted to exist in the artifact with
|
||||
the expected form — a renumber that moved them fails here, not
|
||||
silently downstream.
|
||||
@@ -33,7 +34,8 @@ Subcommands (all print sorted, stable output; non-zero exit on any failure):
|
||||
sweep Residual sweep: the denominator's own classifier (ported from the
|
||||
frozen derivation) over the ten suites at the current tree must
|
||||
find ZERO unconverted verdict-form grep sites; and, IN THE SAME
|
||||
RUN, six per-form specimens planted into a temp copy of a real
|
||||
RUN, eight specimens (six per-form + two absorb-branch probes, #985)
|
||||
planted into a temp copy of a real
|
||||
suite must ALL be found with their correct forms — an instrument
|
||||
that reports zero must first be seen finding what it claims to
|
||||
find (A5).
|
||||
@@ -50,7 +52,9 @@ HERE = Path(__file__).resolve().parent
|
||||
WAKE = HERE.parent
|
||||
ART = HERE / "denominator-089615f.json"
|
||||
|
||||
HEADER_SHIFT = 3 # converter inserted 3 header lines after SCRIPT_DIR in every suite
|
||||
HEADER_SHIFT = 7 # 3 converter header lines after SCRIPT_DIR + 4 lines from the
|
||||
# #984 source guard (1-line `. _wake-common.sh && wake_assert_init` became a 5-line
|
||||
# guarded block) — both uniform across all ten suites, both above every site.
|
||||
|
||||
# The two hand-picked extra arms (base coordinates; forms asserted at load).
|
||||
EXTRA_ARMS = [
|
||||
@@ -68,10 +72,14 @@ RX_ASSIGN_SUB = re.compile(r'=\s*"?\$\(.*grep')
|
||||
RX_IF = re.compile(r"^\s*(el)?if\s+.*grep")
|
||||
RX_GREP = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
|
||||
|
||||
|
||||
def polarity(line):
|
||||
m = RX_FAIL_SAME.search(line)
|
||||
return "OR" if m.group(1) == "||" else "AND"
|
||||
# grep in COMMAND position: at line start or after a command separator / subshell
|
||||
# opener / shell keyword / `!`. Quote-unaware by design — a quoted "grep" after a
|
||||
# separator reads as a command and lands the line in residual, which fails LOUD;
|
||||
# the absorb direction (note) is the one that must never fire on a real verdict.
|
||||
RX_GREP_CMD = re.compile(
|
||||
r"(?:^|[;|&(`]|\$\(|\bif\b|\belif\b|\bthen\b|\belse\b|\bdo\b|\bwhile\b|\buntil\b|!)"
|
||||
r"\s*grep(?:\s|$)"
|
||||
)
|
||||
|
||||
|
||||
def classify(lines):
|
||||
@@ -137,6 +145,28 @@ def classify(lines):
|
||||
return sites, dispo
|
||||
|
||||
|
||||
def residual_sites(lines):
|
||||
"""classify() plus the absorb decision — the ONE path both sweep legs share.
|
||||
|
||||
A classified site is absorbed as a note only when its line carries a wake
|
||||
helper token AND the line shows no grep in command position: a converted
|
||||
line whose PATTERN argument merely contains the word grep. A helper line
|
||||
that also runs a real grep verdict (has_match ... && grep -q SECRET ... &&
|
||||
fail) stays residual (#985). Multi-line forms anchor the site at the line
|
||||
containing grep, so a command-position grep on a continuation line never
|
||||
shares its line with the helper token and stays residual by construction.
|
||||
"""
|
||||
sites, dispo = classify(lines)
|
||||
residual, notes = [], []
|
||||
for ln, form, text in sites:
|
||||
line = lines[ln - 1]
|
||||
if RX_HELPER.search(line) and not RX_GREP_CMD.search(line):
|
||||
notes.append((ln, form, text))
|
||||
else:
|
||||
residual.append((ln, form, text))
|
||||
return residual, notes, dispo
|
||||
|
||||
|
||||
def load_art():
|
||||
art = json.loads(ART.read_text())
|
||||
assert art["total"] == 261 == len(art["rows"]), "artifact self-consistency"
|
||||
@@ -199,13 +229,20 @@ def cmd_arms():
|
||||
return 0
|
||||
|
||||
|
||||
# (expected classify form, expected disposition through residual_sites, snippet)
|
||||
PLANTS = [
|
||||
("A-same-line", ['grep -q needle haystack || fail "plant-A"']),
|
||||
("B-cont-operator", ["grep -q needle haystack ||", ' fail "plant-B"']),
|
||||
("C-cont-backslash", ["grep -q needle \\", ' haystack || fail "plant-C"']),
|
||||
("D-if-form", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
|
||||
("E-count-capture", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
|
||||
("F-extract-capture", ['val="$(grep needle haystack)"']),
|
||||
("A-same-line", "residual", ['grep -q needle haystack || fail "plant-A"']),
|
||||
("B-cont-operator", "residual", ["grep -q needle haystack ||", ' fail "plant-B"']),
|
||||
("C-cont-backslash", "residual", ["grep -q needle \\", ' haystack || fail "plant-C"']),
|
||||
("D-if-form", "residual", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
|
||||
("E-count-capture", "residual", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
|
||||
("F-extract-capture", "residual", ['val="$(grep needle haystack)"']),
|
||||
# G: a converted line that ALSO runs a raw grep verdict — the helper token
|
||||
# must not absorb it (#985)
|
||||
("A-same-line", "residual", ['has_match -q needle "$F" && grep -q SECRET "$F" && fail "plant-G"']),
|
||||
# H: negative control — helper whose PATTERN argument is the word grep;
|
||||
# must be absorbed as a note, never residual
|
||||
("A-same-line", "note", ['has_match -q "grep" haystack || fail "plant-H"']),
|
||||
]
|
||||
|
||||
|
||||
@@ -215,22 +252,20 @@ def cmd_sweep():
|
||||
|
||||
# leg 1: real suites at the current tree must be residual-free
|
||||
for f in suite_files(art):
|
||||
lines = (WAKE / f).read_text().split("\n")
|
||||
sites, _dispo = classify(lines)
|
||||
residual = []
|
||||
for ln, form, text in sites:
|
||||
if RX_HELPER.search(lines[ln - 1]):
|
||||
# converted line whose PATTERN argument contains the word grep:
|
||||
# not an unconverted site, but never silently absorbed either
|
||||
print(f"SWEEP-NOTE {f}:{ln} converted line matches grep-token ({form}): {text[:80]}")
|
||||
continue
|
||||
residual.append((ln, form, text))
|
||||
residual, notes, _dispo = residual_sites((WAKE / f).read_text().split("\n"))
|
||||
for ln, form, text in notes:
|
||||
# converted line whose PATTERN argument contains the word grep:
|
||||
# not an unconverted site, but never silently absorbed either
|
||||
print(f"SWEEP-NOTE {f}:{ln} converted line matches grep-token ({form}): {text[:80]}")
|
||||
for ln, form, text in residual:
|
||||
print(f"SWEEP-RESIDUAL {f}:{ln} {form}: {text[:100]}")
|
||||
bad += 1
|
||||
print(f"SWEEP {f}: {len(residual)} residual verdict site(s)")
|
||||
|
||||
# leg 2, SAME RUN: the instrument must find six per-form plants
|
||||
# leg 2, SAME RUN, SAME PATH as leg 1: the instrument must find every plant
|
||||
# with the right form AND the right absorb disposition — plants G/H exercise
|
||||
# the absorb branch itself, so this leg must go through residual_sites(),
|
||||
# not raw classify()
|
||||
donor = suite_files(art)[0]
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
planted = Path(td) / donor
|
||||
@@ -238,25 +273,28 @@ def cmd_sweep():
|
||||
base_lines = planted.read_text().split("\n")
|
||||
offset = len(base_lines)
|
||||
expect = {}
|
||||
for form, snippet in PLANTS:
|
||||
expect[offset + 1] = form # first physical line of each plant
|
||||
for form, dispo, snippet in PLANTS:
|
||||
expect[offset + 1] = (form, dispo) # first physical line of each plant
|
||||
base_lines.extend(snippet)
|
||||
offset = len(base_lines)
|
||||
planted.write_text("\n".join(base_lines))
|
||||
sites, _ = classify(planted.read_text().split("\n"))
|
||||
found = {ln: form for ln, form, _t in sites if ln in expect}
|
||||
unexpected = [(ln, form) for ln, form, _t in sites if ln not in expect]
|
||||
hits = sum(1 for ln, form in expect.items() if found.get(ln) == form)
|
||||
print(f"SWEEP-PLANTS found={hits}/6 in planted copy of {donor}")
|
||||
if hits != 6:
|
||||
for ln, form in sorted(expect.items()):
|
||||
got = found.get(ln, "<missed>")
|
||||
if got != form:
|
||||
print(f"SWEEP-PLANT-MISS line {ln}: expected {form}, got {got}")
|
||||
residual, notes, _ = residual_sites(planted.read_text().split("\n"))
|
||||
found = {ln: (form, "residual") for ln, form, _t in residual}
|
||||
found.update({ln: (form, "note") for ln, form, _t in notes})
|
||||
unexpected = [(ln, form) for ln, form, _t in residual if ln not in expect]
|
||||
hits = sum(1 for ln, want in expect.items() if found.get(ln) == want)
|
||||
n_plants = len(PLANTS)
|
||||
print(f"SWEEP-PLANTS found={hits}/{n_plants} in planted copy of {donor}")
|
||||
if hits != n_plants:
|
||||
for ln, want in sorted(expect.items()):
|
||||
got = found.get(ln, ("<missed>", "<missed>"))
|
||||
if got != want:
|
||||
print(f"SWEEP-PLANT-MISS line {ln}: expected {want}, got {got}")
|
||||
bad += 1
|
||||
if unexpected:
|
||||
# the donor is a converted suite: any non-plant site the sweep finds
|
||||
# in the copy contradicts the zero it just reported on the original
|
||||
# the donor is a converted suite: any non-plant RESIDUAL site in the
|
||||
# copy contradicts the zero leg 1 just reported on the original
|
||||
# (non-plant notes mirror leg 1's treatment: printed there, not bad)
|
||||
for ln, form in unexpected:
|
||||
print(f"SWEEP-PLANT-UNEXPECTED {donor}(copy):{ln} {form}")
|
||||
bad += 1
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
#
|
||||
# Verified guard per site (line numbers at branch tip, +3 header shift):
|
||||
|
||||
count_lines test-wake-beacon.sh:350 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 349; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-detector.sh:702 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 701; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-hmac.sh:434 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 433; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-quarantine.sh:584 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 583; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-fn-oracle.sh:132 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 131; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-install.sh:434 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 433; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-reconcile.sh:389 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 388; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-ack.sh:741 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 740; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-enqueue-race.sh:208 — red-path summary (with "#927 TOCTOU reproduced (RED)" tail); guard `[ -s "$FAILFILE" ]` at line 207; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-beacon.sh:354 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 353; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-detector.sh:706 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 705; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-hmac.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-quarantine.sh:588 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 587; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-fn-oracle.sh:136 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 135; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-install.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-reconcile.sh:393 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 392; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-ack.sh:745 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 744; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-enqueue-race.sh:212 — red-path summary (with "#927 TOCTOU reproduced (RED)" tail); guard `[ -s "$FAILFILE" ]` at line 211; template execution measured by microtest C11; text verified by static inventory
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# 0. instrument self-test (microtest) — no validate evidence is trusted
|
||||
# before the instrument itself has been proven, including its abort arms.
|
||||
# 1. expected set: 261 coordinates from the FROZEN artifact (+3 header
|
||||
# shift), count asserted against the number declared below BEFORE any
|
||||
# 1. expected set: 261 coordinates from the FROZEN artifact (+7 header
|
||||
# shift: 3 converter lines + 4 #984 guard lines), count asserted against the number declared below BEFORE any
|
||||
# suite runs.
|
||||
# 2. static inventory: converted call sites re-derived from SOURCE TEXT,
|
||||
# must equal the expected set exactly (amendment ONE, leg 1 — the
|
||||
@@ -33,7 +33,8 @@
|
||||
# site's ledger row must already be present (the append lands before the
|
||||
# grep).
|
||||
# 6. residual sweep: the denominator's own classifier finds zero unconverted
|
||||
# verdict greps in the suites — and six per-form plants in the same run.
|
||||
# verdict greps in the suites — and eight plants (six per-form + two
|
||||
# absorb-branch probes, #985) in the same run.
|
||||
#
|
||||
# Output discipline (A10): every line that reports on a suite names the file
|
||||
# under test; exit codes are reported before failure counts.
|
||||
@@ -180,8 +181,15 @@ while read -r helper site form; do
|
||||
bad="$bad no-ARMED-line"
|
||||
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" ||
|
||||
bad="$bad no-ABORT-line"
|
||||
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" &&
|
||||
bad="$bad sentinel-emitted"
|
||||
# AND-polarity check (a match is the defect): a grep error (rc>=2) must be
|
||||
# its own loud arm — it cannot fall through as "no sentinel = pass".
|
||||
rc_sent=0
|
||||
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" || rc_sent=$?
|
||||
case "$rc_sent" in
|
||||
0) bad="$bad sentinel-emitted" ;;
|
||||
1) : ;;
|
||||
*) bad="$bad sentinel-grep-error-rc=$rc_sent" ;;
|
||||
esac
|
||||
grep -q "^${helper} ${site}\$" "$aled" ||
|
||||
bad="$bad no-ledger-row"
|
||||
if [ -z "$bad" ]; then
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"lint": "eslint src",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
||||
"test:framework-shell": "python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mosaicstack/brain": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user