# Tool-result visibility regression test

Regression guard for the observation-masking fix in `core/compaction.js`
(`manageContext`).

## What broke, and what this locks down

`manageContext()` used to call `applyObservationMasking()` **unconditionally on
every loop iteration**, rewriting every `role:'tool'` message older than N
assistant turns to the literal string `'[output hidden]'` — even with compaction
disabled. Two consequences:

1. **Agents went blind** to their own recent tool outputs (read_file, bash,
   web_fetch, …) that they legitimately needed to reason over.
2. **Provider prompt-cache was destroyed** — old history was rewritten every
   single turn, so no request shared a stable prefix with the previous one.

The fix makes masking **opt-in only**
(`settings.compaction.observationMasking === true`, default **OFF**).

This test drives a real veil session through **200 sequential steps** against a
scripted fake provider and asserts, on the actual requests the agent loop sent:

- **(a)** no historical `role:'tool'` message was rewritten to `'[output hidden]'`;
  every tool result still carries its genuine `write_file` observation.
- **(b)** nothing was dropped — the last request carries exactly one tool result
  per completed turn (200).
- **(c)** cache-friendliness — the DB-rendered non-system history at an early
  turn's start is a **byte-identical prefix** of a late turn's start (old history
  is never rewritten between requests).

It then re-runs with `observationMasking: true, observationMaskingTurns: 2`
(**discrimination run**) and asserts masking **does** occur, proving the test
actually detects the regression: guards (a) and (c) flip to FAIL under that
config.

## How it works

- `scripted-api.js` — an OpenAI-compatible fake provider that **generates** a
  deterministic tool-call loop instead of replaying recordings (which is what
  lets us run hundreds of steps with no recording). It speaks the exact SSE wire
  format by reusing `../lib/replay.js`. It **captures every incoming request
  body** and exposes them at `/__captured/summary` and `/__captured/at?i=IDX`.
- `run-visibility-test.js` — mirrors `../run-scenarios.js`: scaffolds an isolated
  workspace (`.veil` settings + a `builder` agent pointed at the fake provider,
  isolated `HOME`), boots veil, drives `Step 1..N` turns in one session,
  collects the captured requests, and runs the assertions above for both configs.

Compaction is prevented from firing (tiny messages, kimi-k2.5's 262 144-token
window, and `compaction.threshold: 0.995`), so the only thing under test is
masking.

## Run it

```bash
# from the repo root
npm run test:obs-mask
# or directly, with options:
node test/ai-completion-test-api/observation-masking/run-visibility-test.js \
    --turns 200 --disc-turns 12 [--keep] [--fake-port 5099] [--veil-port 5399]
```

Exit code `0` = PASS, `1` = FAIL. `--keep` preserves the scratch workspace +
logs under the OS temp dir. `OBS_DEBUG=1` prints the diverging message pair if
the cache-prefix check ever fails.

Nothing here touches production code; it only adds files under `test/` (plus the
`test:obs-mask` npm script).
