flower
/
All briefs
complete draft note flower

mcp-tool-drift: gate validation-brief creation on discovery materiality (stop none/none noise)

canonical · plan

Spec

markdown

hand-off · dispatch

Dispatch

Auto-dispatch

when it reaches planned

Design-loop

design pass before build

This brief is complete — dispatch is closed.

#100 done fresh flower · flower/161-mcp-drift-materiality-gate
agent: claude 1 scratchpad
You are being dispatched from flower Brief #161: mcp-tool-drift: gate validation-brief creation on discovery materiality (stop none/none noise)

Recall pointer:
- Use recall_brief with id 161 for the full folder if you need provenance.

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/161-mcp-drift-materiality-gate
- worktree: not specified
- kind: fresh

Current brief spec:
## Objective
Stop `flower:mcp-tool-drift` from creating cross-harness validation briefs on **description/schema-only** MCP tool changes, and make any brief it *does* create show the **actual** delta. Gate brief creation on **discovery-materiality**, not the raw full-definition hash.

## Problem (grounded in code, 2026-07-03)
- `App\Services\Mcp\McpToolSetSnapshot::capture()` hashes `sha256` over the JSON of each tool's **full canonicalized definition** — name + description + input schema — with tools sorted by name and keys recursively `ksort`ed. So it is **deterministic** (identical tools → identical hash; not spurious churn).
- `App\Console\Commands\McpToolDriftCommand::handle()` creates a validation brief whenever `previousHash !== currentHash`.
- But the brief spec's `Added tools` / `Removed tools` lines come from `nameDiff()` — the tool **name set** only.
- ⇒ Editing any tool's **description or input schema** flips the hash while names are unchanged → a brief is created reading **"Added: none, Removed: none."** It looks spurious, but a definition genuinely changed — it's just never shown.
- Evidence: #65 (`20a663fa → 3fea07f5`, count 41, names identical) plus the abandon pile **#44 / #65 / #83 / #113** (5 of 6 drift briefs abandoned or cancelled). Description/schema-only edits don't affect cross-harness **discovery** (same names, same `tools/list` page-1 membership), so these briefs are low value.
- Source: `flower_feedback` bug **#73** + operator request.

## Change (thin)
1. **Decouple "hash changed" from "create a brief."** Keep hashing the full definition as the snapshot key (so every change is still recorded in the `mcp.toolset.snapshot` Setting), but only **create a validation brief** when the change is **discovery-material**:
   - a tool **name** is added or removed, **OR**
   - the **first-page composition of `tools/list` changes** such that any mutating tool (`brief_append`, `brief_dispatch_complete`, `flower_feedback`) could move on/off page 1 — reuse the first-page assertion already built in `App\Services\Mcp\McpToolValidationService` (Brief #40).
   - Description/schema-only changes with unchanged names **and** unchanged page-1 membership → **update the snapshot only** (optionally log to console/a Setting), **no brief**.
2. **Always show the real delta.** When a brief *is* created, its spec must report what actually changed: name adds/removes **plus** a "definitions changed: `x`, `y`" list (derive per-tool definition hashes from the `tools` array already persisted in the snapshot). Never emit a misleading `none/none`.
3. Preserve the existing `--force` behavior (create a brief even when unchanged) and `--dry-run`.

## Grounding / reuse
- `McpToolValidationService` (Brief #40, already merged) computes the tool set + hash + first-page-completeness assertion → reuse it for the materiality check rather than re-deriving it.
- The snapshot Setting `mcp.toolset.snapshot` already persists `tools` (full per-tool arrays) + `names`, so a per-tool definition diff is computable from stored state.

## Acceptance
- Description/schema-only tool change (names + page-1 membership unchanged) → **no brief**; snapshot updated. Test asserts this (e.g. synthetic description edit).
- A tool name added/removed **OR** a mutating tool crossing the page-1 boundary → **brief created** with an accurate delta (names + changed-definition list); never `none/none`. Tests for both.
- `--force` still creates a brief; `--dry-run` unchanged. `php artisan test` green + `./vendor/bin/pint` on touched files. Commit trailer `Brief: #161`.

## Non-goals / relationships
- Does **not** build the deferred v2 per-harness agent automation (that's Brief #40 v2, which depends on #36's delivery decision).
- Reinforces the auto-dispatch decision: these validation briefs stay **non-auto-dispatch**; this change removes the noise that made blanket auto-dispatch a bad idea.

## Definition of done
The drift detector only briefs on discovery-material changes and always shows the real delta; tests + pint green; ready for dispatch.

Recent/key trace events:
[1] participant_joined flower-refine: (no body)
[2] note_added flower-refine: From flower_feedback bug #73 + operator request (2026-07-03). The scheduled flower:mcp-tool-drift detector creates cross-harness validation briefs on description/schema-only tool edits (name set unchanged), displayed misleadingly as "Added/Removed: none" — noise that fills the abandon pile. Gate brief creation on discovery-materiality and always show the real delta.
[3] plan_proposed flower-refine: ## Objective
Stop `flower:mcp-tool-drift` from creating cross-harness validation briefs on **description/schema-only** MCP tool changes, and make any brief it *does* create show the **actual** delta. Gate brief creation on **discovery-materiality**, not the raw full-definition hash.

## Problem (grounded in code, 2026-07-03)
- `App\Services\Mcp\McpToolSetSnapshot::capture()` hashes `sha256` over the JSON of each tool's **full canonicalized definition** — name + description + input schema — with tools sorted by name and keys recursively `ksort`ed. So it is **deterministic** (identical tools → identical hash; not spurious churn).
- `App\Console\Commands\McpToolDriftCommand::handle()` creates a validation brief whenever `previousHash !== currentHash`.
- But the brief spec's `Added tools` / `Removed tools` lines come from `nameDiff()` — the tool **name set** only.
- ⇒ Editing any tool's **description or input schema** flips the hash while names are unchanged → a brief is created reading **"Added: none, Removed: none."** It looks spurious, but a definition genuinely changed — it's just never shown.
- Evidence: #65 (`20a663fa → 3fea07f5`, count 41, names identical) plus the abandon pile **#44 / #65 / #83 / #113** (5 of 6 drift briefs abandoned or cancelled). Description/schema-only edits don't affect cross-harness **discovery** (same names, same `tools/list` page-1 membership), so these briefs are low value.
- Source: `flower_feedback` bug **#73** + operator request.

## Change (thin)
1. **Decouple "hash changed" from "create a brief."** Keep hashing the full definition as the snapshot key (so every change is still recorded in the `mcp.toolset.snapshot` Setting), but only **create a validation brief** when the change is **discovery-material**:
   - a tool **name** is added or removed, **OR**
   - the **first-page composition of `tools/list` changes** such that any mutating tool (`brief_append`, `brief_dispatch_complete`, `flower_feedback`) could move on/off page 1 — reuse the first-page assertion already built in `App\Services\Mcp\McpToolValidationService` (Brief #40).
   - Description/schema-only changes with unchanged names **and** unchanged page-1 membership → **update the snapshot only** (optionally log to console/a Setting), **no brief**.
2. **Always show the real delta.** When a brief *is* created, its spec must report what actually changed: name adds/removes **plus** a "definitions changed: `x`, `y`" list (derive per-tool definition hashes from the `tools` array already persisted in the snapshot). Never emit a misleading `none/none`.
3. Preserve the existing `--force` behavior (create a brief even when unchanged) and `--dry-run`.

## Grounding / reuse
- `McpToolValidationService` (Brief #40, already merged) computes the tool set + hash + first-page-completeness assertion → reuse it for the materiality check rather than re-deriving it.
- The snapshot Setting `mcp.toolset.snapshot` already persists `tools` (full per-tool arrays) + `names`, so a per-tool definition diff is computable from stored state.

## Acceptance
- Description/schema-only tool change (names + page-1 membership unchanged) → **no brief**; snapshot updated. Test asserts this (e.g. synthetic description edit).
- A tool name added/removed **OR** a mutating tool crossing the page-1 boundary → **brief created** with an accurate delta (names + changed-definition list); never `none/none`. Tests for both.
- `--force` still creates a brief; `--dry-run` unchanged. `php artisan test` green + `./vendor/bin/pint` on touched files. Commit trailer `Brief: #161`.

## Non-goals / relationships
- Does **not** build the deferred v2 per-harness agent automation (that's Brief #40 v2, which depends on #36's delivery decision).
- Reinforces the auto-dispatch decision: these validation briefs stay **non-auto-dispatch**; this change removes the noise that made blanket auto-dispatch a bad idea.

## Definition of done
The drift detector only briefs on discovery-material changes and always shows the real delta; tests + pint green; ready for dispatch.
[4] status_change flower-refine: (no body)
[5] link_added flower-refine: (no body)

Recommended linked context:
{
    "todos": [],
    "scratchpads": [
        {
            "id": 378,
            "solo_scratchpad_id": "1070",
            "name": "flower-refine — reset handoff (2026-07-04)",
            "archived": false,
            "revision": 2
        }
    ]
}

Execution notes:
- Treat the brief as the source of truth.
- Keep work scoped to this dispatch request.
- Use brief_append / brief_update_status when reporting material progress; as your final dispatched-worker step, call brief_dispatch_complete with dispatch_request_id (or brief_id) and actor_ref.
- Codex workers should verify mutating Flower tools with tool_search query `brief_append brief_dispatch_complete flower_feedback` (limit 20) when tool availability is in doubt; report raw SEE/LOAD vs NOT visible instead of silently using local fallbacks.
- Add a git commit trailer `Brief: #161` to every commit for this brief so flower can exact-link commits back to the brief.

provenance · append-only

Trace

live
or paste a screenshot uploading…
  1. link added 1d ago
    agent · system:commit-trailer
  2. participant joined 1d ago
    system · system:commit-trailer
  3. status change 1d ago
    agent · flower-orchestrator
  4. merged 1d ago

    Merged to master as a088359 (branch commit 2a46941; 3-way merge over #159 — verified #159 guard + CompletionGuardTest preserved intact). flower:mcp-tool-drift now gates validation-brief creation on DISCOVERY-MATERIALITY (a tool name added/removed OR a mutating tool crossing the tools/list page-1 boundary); description/schema-only edits refresh the mcp.toolset.snapshot Setting only — no brief. Created briefs always show the REAL delta (names added/removed + "Definitions changed" list + page-1 crossings) — never none/none. Reuses McpToolValidationService first-page assertion (#40); persists page1_mutating in the snapshot for exact future comparison. --force/--dry-run preserved. +McpToolDriftCommandTest coverage. Full suite green (1054 passed, 2 skips). No migration; scheduled command → no Horizon reload.

    agent · flower-orchestrator
  5. status change 1d ago
    agent · flower-161-worker
  6. dispatched 1d ago

    Dispatch request #100 marked done.

    agent · flower-161-worker
  7. note added 1d ago

    Implemented on branch `flower/161-mcp-drift-materiality-gate` (off master 129fbe1), commit 2a46941. **Change (thin):** 1. McpToolDriftCommand now decouples "full-def hash changed" from "create a brief". It still hashes the full canonicalized definition as the snapshot key (every change recorded in the `mcp.toolset.snapshot` Setting), but only creates a validation brief when the change is discovery-material: a tool name added/removed, OR a mutating tool crossing the tools/list page-1 boundary. Reuses McpToolValidationService's first-page assertion (Brief #40) via a new `discoverySignatureFromTools()` helper; persists `page1_mutating` in the snapshot for exact future comparisons (legacy snapshots without it reconstruct the signature from stored `tools`). Description/schema-only edits (names + page-1 membership unchanged) refresh the snapshot only — no brief. 2. When a brief IS created it always shows the real delta: names added/removed PLUS a "Definitions changed" list (per-tool fingerprints, normalized so live stdClass empties and JSON-persisted `[]` empties fingerprint identically) PLUS first-page mutating crossings — never a misleading none/none. Brief meta.diff carries added/removed/definitions_changed/page1_added/page1_removed. 3. `--force` (brief even when unchanged) and `--dry-run` preserved. **Tests (tests/Feature/McpToolDriftCommandTest.php):** description-only edit → no brief + snapshot refreshed; name addition → brief with accurate delta (asserts NOT `Added tools: none`); mutating tool crossing page-1 boundary → brief with `First-page mutating tools added: flower_feedback`; --force → brief when unchanged; --dry-run → no brief, setting untouched. **Verify:** `php artisan test` green (1051 tests: 1048 passed, 3 skipped, 0 failing); `./vendor/bin/pint` clean on the 3 touched files (McpToolDriftCommand.php, McpToolValidationService.php, McpToolDriftCommandTest.php). No push; orchestrator merges.

    agent · flower-161-worker
  8. participant joined 1d ago
    system · flower-161-worker
  9. dispatched 1d ago

    Dispatch request #100 queued for flower.

    agent · flower-orchestrator
  10. status change 1d ago
    agent · flower-orchestrator
  11. participant joined 1d ago
    system · flower-orchestrator
  12. link added 1d ago
    agent · flower-refine
  13. status change 1d ago
    agent · flower-refine
  14. plan proposed 1d ago

    ## Objective Stop `flower:mcp-tool-drift` from creating cross-harness validation briefs on **description/schema-only** MCP tool changes, and make any brief it *does* create show the **actual** delta. Gate brief creation on **discovery-materiality**, not the raw full-definition hash. ## Problem (grounded in code, 2026-07-03) - `App\Services\Mcp\McpToolSetSnapshot::capture()` hashes `sha256` over the JSON of each tool's **full canonicalized definition** — name + description + input schema — with tools sorted by name and keys recursively `ksort`ed. So it is **deterministic** (identical tools → identical hash; not spurious churn). - `App\Console\Commands\McpToolDriftCommand::handle()` creates a validation brief whenever `previousHash !== currentHash`. - But the brief spec's `Added tools` / `Removed tools` lines come from `nameDiff()` — the tool **name set** only. - ⇒ Editing any tool's **description or input schema** flips the hash while names are unchanged → a brief is created reading **"Added: none, Removed: none."** It looks spurious, but a definition genuinely changed — it's just never shown. - Evidence: #65 (`20a663fa → 3fea07f5`, count 41, names identical) plus the abandon pile **#44 / #65 / #83 / #113** (5 of 6 drift briefs abandoned or cancelled). Description/schema-only edits don't affect cross-harness **discovery** (same names, same `tools/list` page-1 membership), so these briefs are low value. - Source: `flower_feedback` bug **#73** + operator request. ## Change (thin) 1. **Decouple "hash changed" from "create a brief."** Keep hashing the full definition as the snapshot key (so every change is still recorded in the `mcp.toolset.snapshot` Setting), but only **create a validation brief** when the change is **discovery-material**: - a tool **name** is added or removed, **OR** - the **first-page composition of `tools/list` changes** such that any mutating tool (`brief_append`, `brief_dispatch_complete`, `flower_feedback`) could move on/off page 1 — reuse the first-page assertion already built in `App\Services\Mcp\McpToolValidationService` (Brief #40). - Description/schema-only changes with unchanged names **and** unchanged page-1 membership → **update the snapshot only** (optionally log to console/a Setting), **no brief**. 2. **Always show the real delta.** When a brief *is* created, its spec must report what actually changed: name adds/removes **plus** a "definitions changed: `x`, `y`" list (derive per-tool definition hashes from the `tools` array already persisted in the snapshot). Never emit a misleading `none/none`. 3. Preserve the existing `--force` behavior (create a brief even when unchanged) and `--dry-run`. ## Grounding / reuse - `McpToolValidationService` (Brief #40, already merged) computes the tool set + hash + first-page-completeness assertion → reuse it for the materiality check rather than re-deriving it. - The snapshot Setting `mcp.toolset.snapshot` already persists `tools` (full per-tool arrays) + `names`, so a per-tool definition diff is computable from stored state. ## Acceptance - Description/schema-only tool change (names + page-1 membership unchanged) → **no brief**; snapshot updated. Test asserts this (e.g. synthetic description edit). - A tool name added/removed **OR** a mutating tool crossing the page-1 boundary → **brief created** with an accurate delta (names + changed-definition list); never `none/none`. Tests for both. - `--force` still creates a brief; `--dry-run` unchanged. `php artisan test` green + `./vendor/bin/pint` on touched files. Commit trailer `Brief: #161`. ## Non-goals / relationships - Does **not** build the deferred v2 per-harness agent automation (that's Brief #40 v2, which depends on #36's delivery decision). - Reinforces the auto-dispatch decision: these validation briefs stay **non-auto-dispatch**; this change removes the noise that made blanket auto-dispatch a bad idea. ## Definition of done The drift detector only briefs on discovery-material changes and always shows the real delta; tests + pint green; ready for dispatch.

    agent · flower-refine
  15. note added 1d ago

    From flower_feedback bug #73 + operator request (2026-07-03). The scheduled flower:mcp-tool-drift detector creates cross-harness validation briefs on description/schema-only tool edits (name set unchanged), displayed misleadingly as "Added/Removed: none" — noise that fills the abandon pile. Gate brief creation on discovery-materiality and always show the real delta.

    agent · flower-refine
  16. participant joined 1d ago
    system · flower-refine

epic · dependencies

Relationships

epic parent

depends on

No dependencies — dispatchable once planned.

agents · waves

Participants

  • flower-refine participant · active
  • flower-orchestrator participant · active
  • flower-161-worker participant · active
  • system:commit-trailer participant · active

trace · graph

Links

  • Commit #3999 execution
  • Scratchpad #378 execution

scope

Projects

  • flower · primary

dogfood · read-only

Agent’s-eye view

The literal recall_brief payload an agent gets — same service path as the MCP tool.