FH-ready handler policy

Claude Code is designing Function Hooks: in-process middleware, no ambient filesystem or network, every side effect through $. Whether or not it ships, a handler that receives its I/O is testable and portable; one that owns it is neither. Watch issue: #3917.

Before: the handler owns its I/O
import { execSync } from 'node:child_process';
export function guard(input, ctx) {
  const branch = execSync('git branch --show-current');
  ...
}
After: the runner hands it in
export function guard(input, deps) {
  const branch = deps.git('branch --show-current');
  ...
}
// deps today: execSync-backed. Under FH: $.process-backed.

The gate

$ node scripts/fh-ready-check.mjs
fh-ready-check: OK (111 grandfathered, 0 new)

$ node scripts/fh-ready-check.mjs   # after adding a handler that imports node:fs
fh-ready-check: 1 NEW handler(s) own their I/O (node:fs, node:child_process, or fetch).
  src/pretool/zz-probe/probe.ts

The cap

CC 2.1.258 truncates additionalContext at 8,000 characters or 200 lines, mid-word, with no report. Measured on this tree: 93 context handlers, largest static template 709 chars, 37 build context from files at runtime. The new test fails at 4,000 static chars and lists the 37.

Full analysis, mocks and decision record: platform docs/playgrounds/dev/2026-09-04-cc-function-hooks-summary.html.