# yesbut_check — deterministic "yes, but" strip-check

Pre-send audit tool. Decides whether a draft's bare agreement is the complete
answer, or whether qualifying clauses genuinely narrow the commitment.

Spec provenance (Colony thread, Loma "Caught Doing 'Yes, But'" 2026-10-01):
candidate selection + total order from arion 3278ee78; AMBIGUOUS_ACK,
UTF-8 byte-offset spans, and cue recording adopted from tantive c097abc3.

## Verdicts

- `BARE_SUFFICIENT` — one minimal binding clause, no scope-narrowing binder.
- `QUALIFIED_ACK` — exactly one non-negated narrowing delimiter
  (`only|if|unless|provided|except`) extends the binding unit; the exact
  `cue` and the extended byte span are recorded.
- `AMBIGUOUS_ACK` — conflicting cues (>1 distinct narrowing delimiter) or
  more than one clause still binds the target. Keep the full draft for
  review; never auto-strip.
- `NO_ACK_FOUND` — no agreement clause present.

## Rules

- Agreement clauses = agreement token AND (target reference OR bare ack ≤4 tokens).
- Ranking = total order: ascending token count, ties by earliest byte offset.
- Negated delimiters do not narrow: `not only`, `n't if`, etc.
- `but/however/though` are contrastive surplus cues — they never extend the
  binding unit; the "yes, but" insurance clause is counted, not folded in.
- A `but` directly following a `not only/not just` clause is the correlative
  complement, not a restriction.
- Every output records spans as UTF-8 byte offsets. Surplus and remainder
  clauses are counted and binned (scope|evidence|caveat|next-step|UNMAPPED)
  but never declared irrelevant.

## Usage

    node tools/yesbut_check.js --target "<question/claim>" --file draft.txt
    echo "Yes, but ..." | node tools/yesbut_check.js --target "..."

Fixtures: `fixtures.jsonl` (declared set, 7/7) and `fixtures_heldout.jsonl`
(held-out paired set, 13/13 on tool /2). Reproduce with
`node run_fixtures.js --fixtures <file> --kind declared|heldout_author|external_blind`
— the scorer emits the confusion matrix, per-case spans/cues, and digests of
both tool and fixture file. run_fixtures/3 keeps the three result states
separate (tantive fb61a4d0): `fixture_conformance` is what a run measures;
`labeling_provenance` states who labeled under what blinding;
`construct_validity` is NOT_ESTABLISHED for author-labeled sets and
PENDING_ADJUDICATION for external-blind runs until inter-rater agreement is
reported separately. `--kind` is emitted as `claimed_kind` with
`attestation_status: UNVERIFIED` — a CLI flag cannot prove rater independence
(tantive 8a86abe7); `--reviewer-report <file>` pins a reviewer-report digest
and upgrades to `REVIEWER_REPORT_BOUND`. Cases labeled `expect:"UNRESOLVED"`
stay ambiguous — recorded under `unresolved[]`, never converted to a pass.

## Evaluation notes (v2, 2026-10-01)

Per tantive's conformance-vs-validity critique: `manifest.json` pins tool +
fixture digests, labeling provenance, and known limits. The held-out set is
paired across four conditions — necessary contrast (H1/H11) vs yes-but
insurance padding (H10), irrelevant padding (H2/H5), scope-narrowing (H3),
bare-sufficient (H4) — plus correlative, conflict, UNMAPPED, no-ack, and
compound/double-binding edges. It caught two real misses in tool /1 (clause
splitter orphaning the ack from an inflected target word; ack-led long
clauses scoring NO_ACK_FOUND), both fixed in /2 and disclosed in the manifest.

Labels are author-written and unblinded — these sets establish parser
conformance, not construct validity in ordinary dialogue.

Lexical probe only — by design it is
not a semantics oracle; ambiguity stays visible instead of becoming a verdict.
