# V16.14 — Ultra-Fast Token Economy + Runtime Correctness Hardening

## Why this release exists

V16.13 made the research path fast and mostly network-free. V16.14 does not add
new capabilities; it makes the existing ones **provably correct under hostile or
degraded conditions**, and it stops the runtime from paying for work that cannot
change a decision.

Two problems motivated the release:

1. **Runtime correctness.** V16.13 handed the trusted research path to
   `fetch(..., { redirect: "follow" })`. That follows an attacker-controlled
   redirect with no SSRF re-check, cannot see that a public hostname resolves to
   a private address, and leaves an orphan socket running after a
   `Promise.race` timeout fires.
2. **Token economy.** Several shapers reported a bound they did not honor (a
   head/tail plus an unbounded omission notice), and the runtime could pay for a
   DeepSeek synthesis turn whose bounded input contained nothing to synthesize.

## Laws

- **L1 Public HTTPS only, resolved before connect.** A hostname is resolved,
  every resolved address is validated, and the validated addresses are pinned
  into the TLS socket's `lookup`. A name that resolves to a private address is
  `BLOCKED_POLICY` even when the hostname text looks public.
- **L2 Redirects are manual and bounded.** Every hop is canonicalized,
  scheme-checked, credential-checked, re-resolved and re-validated. A public →
  private redirect is `BLOCKED_POLICY`; the hop count is capped; a `Location`
  carrying credentials is rejected.
- **L3 Cancellation is real.** An `AbortSignal` destroys the in-flight socket.
  A soft/hard deadline therefore stops the request instead of orphaning it.
- **L4 Credentials never travel sideways.** The GitHub token is attached only to
  approved GitHub hosts and is stripped when a redirect crosses to another host.
- **L5 Claims bind by content hash.** A high-risk claim (security, version,
  breaking, install, auth, config, migration, api-surface) that is supported only
  by a `sourceId` string is downgraded to `PARTIALLY_SUPPORTED` with
  `binding: "sourceId-only"`. It can never be presented as fully `SUPPORTED`.
- **L6 Freshness is revalidated, not re-downloaded.** A stale cache entry with a
  validator performs a conditional GET; a `304` reuses the stored body.
- **L7 Bytes are bytes, chars are chars, tokens are tokens.** Byte metrics use
  `Buffer.byteLength(text, "utf8")`. A JS string length is never called "bytes".
  A char count is never promoted to a token count.
- **L8 Provider tokens are MEASURED or NOT_MEASURED.** A token count is
  `MEASURED` only from real provider usage telemetry. Chars → tokens is
  `ESTIMATED`. An unmeasured run emits **no** token row at all, so a downstream
  aggregate cannot mistake it for a measured zero.
- **L9 The budget is hard.** The context kernel and the tool-output budgeter
  never emit more than the budget they were given, **including** the omission
  notice they append.
- **L10 DeepSeek is not paid for an empty prompt.** Synthesis is refused when
  the bounded input has no conflict, no unknown and no captured fact; the refusal
  is recorded as an `unnecessary-deepseek-call` waste signal.
- **L11 A noticed waste signal is observable, never a verdict.** Every waste
  signal a run produces is emitted as a `research-waste:<signal>` observation
  through the broker's existing `toEfficiencyEvents()` producer path, so the
  V16.12 waste detector and Metrics V2 can see it. The observation carries the
  occurrence count the run measured and **no verdict**; aggregation remains with
  the single metrics authority (`efficiency-metrics-v16-10`).

## Capabilities

| # | Owner | Question |
|---|-------|----------|
| A | `lib/research-transport-v16-14.mjs` | What is a safe, bounded, cancellable HTTPS exchange? |
| B | `lib/research-network-policy-v16-13.mjs` | Is this egress safe (URL text **and** resolved address)? |
| C | `lib/external-research-broker-v16-13.mjs` | How do we compose, cancel, and capsule — and what must we refuse to pay for? |
| D | `lib/research-page-fetch-v16-13.mjs` | How do we fetch, scope credentials, and revalidate? |

Module A is the only genuinely new single owner. Everything else is an in-place
hardening of the module that already owned the question: no second Evidence
Store, no second verification authority, no second Metrics authority, no second
workspace-truth owner, and no always-on daemon.

## Verification

Research never produces PASS. The local verifier remains the only PASS authority.
DeepSeek remains an untrusted advisor; unsourced claims stay
`UNVERIFIED_ADVISOR_CLAIM`, and advisor output is never evidence.

## Non-goals / explicit limits

No new network capability, no crawler, no browser pool, no vector DB, no paid
routing, no autonomous publish, no GitHub Actions.

## Benchmark honesty

`scripts/bench-v16-14-economy.mjs` reports `synthetic: true`,
`claimStatus: "SIMULATED_ONLY"`, `providerTokens: "NOT_MEASURED"` and
`tokenColumnsProvenance: "ESTIMATED"`. Cache HIT and MISS are separate cells and
are never averaged. The report carries `summedSpeedupClaim: null`: cells are
per-scenario and are never summed into one speedup number.

Byte and char columns are reported separately, so a byte/char confusion in any
future report is visible rather than silent.
