# O9 — лимит доли INFERRED: Implementation Plan

> **For agentic workers:** исполнение INLINE под стеной (субагенты запрещены машиной, VA-4).
> REQUIRED SUB-SKILL: superpowers:test-driven-development. Шаги — упорядоченный указатель стены.

**Goal:** Добавить лимит доли INFERRED (< 0.5 строго) в `verifyArtifact` (`tools/context-verity.mjs`),
закрыв O9 (§5.5 первоисточника). TDD, регрессия tools-only 3829 → ~3837 GREEN, 0 регрессий.

**Architecture:** Чистая правка `verifyArtifact`: новый счётчик `inferredCount`, экспортируемая
константа `INFERRED_SHARE_MAX = 0.5`, предикат `inferredShareOk` в составе `ok`. Поведение
существующих проверок (VA-9 / VF-1 / SE-A1 / downgrade) не меняется; `artifactHasUnresolvedExtracted`
(страж печати freeze-gate) читает только `.downgraded` — O9 его не затрагивает.

**Tech Stack:** Node ESM (.mjs), vitest (tools-config). Регрессия — БАРЕ:
`npx vitest run --root app --config vitest.config.tools.mjs` (без cd/&&/пайпа).

---

## Проверенный контекст

```verified-context-json
[
  {"id":"c1","claim":"verifyArtifact — целевая функция O9","ref":"tools/context-verity.mjs","kind":"EXTRACTED","anchor":"verifyArtifact"},
  {"id":"c2","claim":"extractedCount уже считается — основа знаменателя share","ref":"tools/context-verity.mjs","kind":"EXTRACTED","anchor":"extractedCount"},
  {"id":"c3","claim":"MIN_ANCHOR_LENGTH — образец экспортируемого порога-константы","ref":"tools/context-verity.mjs","kind":"EXTRACTED","anchor":"MIN_ANCHOR_LENGTH"}
]
```

## Шаги (запечатываемая последовательность)

```steps-json
[
  {"op":"Edit","object":"tools/context-verity.test.mjs","ref":"o9"},
  {"op":"Edit","object":"tools/context-verity.test.mjs","ref":"o9"},
  {"op":"Bash","object":"npx vitest run --root app --config vitest.config.tools.mjs context-verity","ref":"o9"},
  {"op":"Edit","object":"tools/context-verity.mjs","ref":"o9"},
  {"op":"Bash","object":"npx vitest run --root app --config vitest.config.tools.mjs context-verity","ref":"o9"},
  {"op":"Bash","object":"npx vitest run --root app --config vitest.config.tools.mjs","ref":"o9"}
]
```

---

### Task 1: Тесты O9 (Edit ×2 теста) + RED

**Files:** Modify/Test: `tools/context-verity.test.mjs`

- [ ] **Шаг 1 (steps-json #1): правка существующего теста 50/50 под новый порог.**
  Тест «INFERRED не требует резолва» был 1 EXTRACTED + 1 INFERRED (share=0.5) → с O9 это ok:false.
  Интент теста (INFERRED не резолвится) сохраняем, добавив 2-й EXTRACTED (share=1/3<0.5).

- [ ] **Шаг 2 (steps-json #2): добавить 7 O9-тестов + `import { INFERRED_SHARE_MAX }`.**

```js
import { INFERRED_SHARE_MAX } from './context-verity.mjs';

describe('verifyArtifact — лимит доли INFERRED (O9)', () => {
  const read = (file) => file === 'a.mjs' ? 'function good(){}' : null;
  const EXT = (id) => ({ id, claim: 'x', ref: 'a.mjs:1', kind: 'EXTRACTED', anchor: 'good' });
  const INF = (id) => ({ id, claim: 'y', ref: 'a.mjs:1', kind: 'INFERRED', derivation_ref: 'd' });

  it('INFERRED_SHARE_MAX экспортирован и равен 0.5', () => {
    expect(INFERRED_SHARE_MAX).toBe(0.5);
  });
  it('доля INFERRED < 0.5 (2 EXTRACTED + 1 INFERRED) → ok сохраняется', () => {
    const r = verifyArtifact([EXT('e1'), EXT('e2'), INF('i1')], read);
    expect(r.ok).toBe(true);
    expect(r.inferredCount).toBe(1);
  });
  it('доля INFERRED ровно 0.5 → не ok + flagged O9', () => {
    const r = verifyArtifact([EXT('e1'), INF('i1')], read);
    expect(r.ok).toBe(false);
    expect(r.flagged.some((f) => /O9/.test(f.reason))).toBe(true);
  });
  it('доля INFERRED > 0.5 (1 EXTRACTED + 2 INFERRED) → не ok', () => {
    const r = verifyArtifact([EXT('e1'), INF('i1'), INF('i2')], read);
    expect(r.ok).toBe(false);
  });
  it('пустой артефакт (total 0) → не ok по VA-9, без O9-флага', () => {
    const r = verifyArtifact([], read);
    expect(r.ok).toBe(false);
    expect(r.flagged.some((f) => /O9/.test(f.reason))).toBe(false);
  });
  it('1 EXTRACTED + 99 INFERRED → не ok (атака §5.5-R6.3)', () => {
    const art = [EXT('e1')];
    for (let i = 0; i < 99; i++) art.push(INF('i' + i));
    const r = verifyArtifact(art, read);
    expect(r.ok).toBe(false);
    expect(r.inferredCount).toBe(99);
  });
  it('downgraded-из-EXTRACTED не инфлейтит inferredCount', () => {
    const r = verifyArtifact([{ id: 'd1', claim: 'x', ref: 'a.mjs:1', kind: 'EXTRACTED', anchor: 'absent' }], read);
    expect(r.inferredCount).toBe(0);
    expect(r.ok).toBe(false);
  });
});
```

- [ ] **Шаг 3 (steps-json #3): RED.**
  Run: `npx vitest run --root app --config vitest.config.tools.mjs context-verity`
  Expected: FAIL — `context-verity.mjs` не экспортирует `INFERRED_SHARE_MAX` → файл не грузится.

---

### Task 2: Реализация лимита (Edit impl) + GREEN

**Files:** Modify: `tools/context-verity.mjs`

- [ ] **Шаг 4 (steps-json #4): const `INFERRED_SHARE_MAX` + переписать `verifyArtifact`.**
  Добавить перед JSDoc `verifyArtifact`:

```js
// O9 (§5.5 — лимит доли INFERRED, решение владельца 2026-06-13 «< 0.5 строго»): доля INFERRED
// в проверенном контексте обязана быть СТРОГО меньше порога — большинство контекста твёрдые
// EXTRACTED. Второй рубеж поверх VA-9/VF-1: без него 1 EXTRACTED + масса INFERRED проходит
// формально (§5.5-R6.3). Клапан INFERRED-тяжёлых артефактов — owner-sign-off/escape (вне модуля).
// Экспортируемая константа (зеркало MIN_ANCHOR_LENGTH) — порог тонкой настройки, design-choice.
export const INFERRED_SHARE_MAX = 0.5;
```

  Изменения в `verifyArtifact`: завести `let inferredCount = 0;`; ветку INFERRED сделать
  `else if (e.kind === 'INFERRED') { inferredCount++; if (!derivation_ref) {…VF-1…} }`;
  после цикла — `total = extractedCount + inferredCount`,
  `inferredShareOk = total === 0 ? true : inferredCount / total < INFERRED_SHARE_MAX`,
  при `!inferredShareOk` → `flagged.push({id:null, reason:'…(O9)'})`; в `ok` добавить
  `&& inferredShareOk`; в return добавить `inferredCount`.

- [ ] **Шаг 5 (steps-json #5): GREEN.**
  Run: `npx vitest run --root app --config vitest.config.tools.mjs context-verity`
  Expected: PASS — все context-verity-тесты зелёные (старые + 7 новых O9 + правленый 50/50).

---

### Task 3: Регрессия

- [ ] **Шаг 6 (steps-json #6): полный tools-набор.**
  Run: `npx vitest run --root app --config vitest.config.tools.mjs`
  Expected: PASS — ~3837 (база 3829 + 7 O9), 0 регрессий в соседних машинах. Сверять ЗЕЛЁНОСТЬ
  (SE13), не точное число. Коммит — владелец после GREEN.

---

## Self-Review

- **Spec coverage:** §5.5 порог (#1) → Task 2 const; знаменатель 0 (#2) → Task 2 формула; формула (#3)
  → Task 2; наблюдаемость/inferredCount → Task 2 return; тесты → Task 1. ✅
- **Граница:** `artifactHasUnresolvedExtracted` (freeze-gate) читает `.downgraded`, не `.ok` —
  O9 не меняет путь печати. Единственный затронутый существующий тест — 50/50 (Шаг 1). ✅
- **Типы:** `verifyArtifact` → `{ok, flagged, downgraded, extractedCount, inferredCount, entries}`
  (добавлено `inferredCount`); `INFERRED_SHARE_MAX:number`. ✅
