# MiniMax-M3 cross-model dice value — flaky-tail recall

date: 2026-06-24 · harness: opencode v1.15.4 + continue-on-truncation wrapper · model: `openrouter/minimax/minimax-m3`

## BLUF
**MiniMax-M3 has POSITIVE cross-model dice value on the recall flaky-tail** — the opposite of its result on the C02/C09 discriminator. On cells where the primary detector *sometimes misses*, independent minimax rolls raise union recall:
- **S2-team-owner-invariant** (owner demote/promote backdoor; Sonnet 3/9 ≈ 33%): minimax **1/3 emitted right-reason catch**.
- **S3-task-assignee-membership-zyncapi** (opaque-FK assignee membership IDOR; baseline prompt 2/3): minimax **3/3 emitted right-reason catch**.

This is the intended use the C02 probe ruled OUT: C02/C09 is a *hard blind spot* (minimax 0/3, can't union what you never catch); S2/S3z are *flaky tails* (catch rate < 1.0), exactly where a cheap independent model lifts the union. See `2026-06-24-opencode-minimax-m3-c02-capability.md` for the C02 result.

## Wrapper (gate C — built + validated)
`scratchpad/run_review.py`. MiniMax over-reasons and the provider truncates (`finish='other'`, no text part) before emitting the answer — measured on the C02 probe. The wrapper:
- runs opencode `run --format json`, reads the final text part + finish reason from `opencode.db`;
- **continue-on-truncation**: on empty text OR `finish != 'stop'`, resumes the SAME session (`run -s <sid>`, analysis preserved) with a directive "emit the findings now, no tools, no further reasoning" prompt, ≤2 retries;
- reasoning is NOT capped (capability test wants full reasoning, then forced output);
- sums cost across every assistant turn in the session.

Validated for $0.0153 by resuming the truncated C02 roll-3 session → it emitted a full 16k-char findings list, no re-spiral. Fired once in the wild (S3z roll 2, recovered to `finish=stop`).

**Residual gap:** the wrapper recovers a *truncated* turn (subprocess returns) but NOT a *hung* turn (subprocess never returns within timeout). On `safe.ts` minimax spiraled past 1500s and the call was killed before any assistant message persisted — see Precision below.

## Method (parity + blind)
- Targets reconstructed to NEUTRAL basenames in isolated git-root arenas (`arena_s2/team.ts`, `arena_s3z/tasks.ts`) — stops opencode's upward project-root walk and the dir-name path-leak (a defect-precise dir primes the model; see memory `single-file-cell-path-leak`). No CLAUDE.md/AGENTS.md in any parent.
- Prompt = `domains/security/detectors/baseline/baseline.prompt.txt` verbatim, `{{MODULE_PATH}}`→basename. Same detector contract as the Sonnet/Cursor runs.
- Cells are the real corpus `vuln.ts`, leak-checked (no BUG/IDOR/owner-invariant annotations; `assignee_id` hits are legit code).
- Blind: model never saw canonical.json, fix commit, or audit docs. Graded by hand against canonical right-reason criteria.

## Results

### S2-team-owner-invariant (canonical: credit ONLY a finding naming the owner demote/promote backdoor — NOT generic coarse-RBAC; the perm-subset anti-escalation check is already correct and is NOT the bug)

| roll | emitted | catch? | note |
|------|---------|--------|------|
| 1 | 8 findings, 41k chars | **CATCH (title/body tension)** | Finding 3 *title* blames the perm-subset anti-escalation check (which the canonical says is ALREADY correct, NOT the bug); the *body+fix* name the protected-role invariant: "reject granting any role whose id/name equals a protected role (e.g. `owner`) unless the actor is already that role", contrasted with the transfer path, and quote the decisive fix. Body-over-title — defensible and auditable, but it is the borderline catch. |
| 2 | 2–3 findings, 7k chars | MISS | finish=`stop` but only 2–3 findings vs 8–9 elsewhere — likely an early-stop, not a deep miss. Covered the transfer endpoint (atomicity, cross-tenant membership); quoted the DELETE owner-guard but did not flag PATCH-role lacks it. (The wrapper resumes only on empty/non-`stop`, so a short-but-`stop` emission is NOT completeness-checked — see footnote.) |
| 3 | 9 findings, 9k chars | MISS | Finding 3 = generic cross-tenant IDOR on `targetUserId`; no owner-protection invariant. |

**S2: 1/3 emitted right-reason.** The headline ("positive recall") does NOT hinge on this borderline roll-1 catch — S3z 3/3 (below) carries it independently. Independent rolls → union-additive (OR-ing detectors cannot lower union recall); the union point needs only minimax > 0, no cross-grade comparison to Sonnet's remembered 3/9.

> Footnote (roll 2 early-stop): `run_review.py` resumes only on empty text OR `finish != 'stop'`. A turn that emits few findings and stops cleanly passes through without a completeness check. Roll-2's short `stop` emission could understate its catch; counted as MISS conservatively (it did not name the invariant in what it emitted).

### S3-task-assignee-membership-zyncapi (canonical: credit naming the absent membership check on `assignee_id` → cross-tenant assignment IDOR; must single out assignee_id, not flag FKs generically)

| roll | emitted | catch? | finding |
|------|---------|--------|---------|
| 1 | 12 findings, 13k chars | **CATCH** | #5 "`assignee_id` is permission-checked but not tenant-membership-checked" |
| 2 | 12 findings, 17k chars (1 truncation recovered) | **CATCH** | #3 "`assignee_id` not validated as a member of the tenant/project" — fix: look up assignee_id against membership |
| 3 | 10 findings, 10k chars | **CATCH** | #4 "`assignee_id` not validated against `session.tid` — BOLA on assignment" |

**S3z: 3/3 emitted right-reason**, and it discriminates (notes assignee is the unchecked FK while the route's RBAC IS present) — beats the baseline-prompt 2/3 on this sub-shape.

## Cost + tokens (per roll, model cost from opencode.db)

Tokens summed across every assistant turn in the session (db `message.data.tokens`), in/out/reason/total.

| cell | roll | cost | finish | resumes | tokens (in / out / reason / total) |
|------|------|------|--------|---------|------------------------------------|
| S2 | 1 | $0.0240 | stop | 0 | 34518 / 10325 / 29 / 65992 |
| S2 | 2 | $0.0358 | stop | 0 | 32512 / 2096 / 16661 / 110277 |
| S2 | 3 | $0.0189 | stop | 0 | 16114 / 2388 / 7372 / 65298 |
| S3z | 1 | $0.0354 | stop | 0 | 34576 / 3418 / 15506 / 92412 |
| S3z | 2 | $0.0464 | stop | 1 (recovered) | 81568 / 17855 / 46 / 107277 |
| S3z | 3 | $0.0231 | stop | 0 | 16037 / 2595 / 10707 / 68891 |

(total > in+out+reason because total includes cache read/write tokens.)

S2 k=3 = $0.079; S3z k=3 = $0.105; wrapper validation = $0.015. Total dice-value probe ≈ **$0.20** (OR-delta). OR budget remaining ≈ $1.18.

## Precision-on-safe (NOT completed — blocked by over-reasoning hang)
The recall numbers above prove union-additivity. The OPEN question is precision: does minimax also RED-on-safe (false-flag `assignee_id` membership on `safe.ts`, where `getMembership` IS present)? The canonical's `green_on_safe` is `xfail` — band-1 single-file is known to FP here. **Attempted, blocked:** on `safe.ts` minimax spiraled past the 1500s timeout with no completion (worse than on vuln cells — no obvious defect to anchor reasoning). To measure precision, run with a bounded reasoning budget (`OPENCODE_CONFIG_CONTENT` openrouter `reasoning.max_tokens`) or a hang-tolerant wrapper (kill → resume). A 3/3 recall catcher that also FPs on safe trades recall lift for triage noise — precision must be measured before adopting minimax as a union member.

## Verdict
- **Dice value: POSITIVE on the flaky tail.** Cheap independent minimax rolls (~$0.02–0.05) lift union recall on cells the primary detector misses sometimes (S2 33%, S3z 2/3). This is the role the C02 probe could not justify.
- **Use minimax for: recall-union dice on flaky-tail cells**, NOT as a discriminator for hard blind spots (C02/C09 = 0/3).
- **Gate before adoption:** (1) precision-on-safe must be measured (blocked above); (2) the over-reasoning hang needs a bounded-reasoning or hang-tolerant harness — the plain `opencode run` truncates/hangs ~1/3 of the time.
