# OpenChrome v1.11.1

Release date: 2026-05-12

## Summary

v1.11.1 is a patch release on top of v1.11.0. It contains **no new
runtime code** beyond v1.11.0 — only a comprehensive release note that
documents the full cumulative diff from v1.10.4 (the last v1.10.x npm
publish) to v1.11.1, plus the version bump.

Treat this release note as the **canonical reference** for what changed
in the v1.11 line. Anyone upgrading from v1.10.x reads this single
document and gets the complete picture: every new module, every new
MCP surface entry, every behavioral change, and every breaking-change
flag they need to verify.

The v1.11 line is the **portability-harness contract** release. It
establishes a durable `core` / `pilot` tier split inside `openchrome-mcp`
and ships a substantial batch of harness primitives.

---

## Upgrade

```bash
npm install -g openchrome-mcp@1.11.1
openchrome check      # verify the installed CLI and runtime wiring
```

Fresh install:

```bash
npm install -g openchrome-mcp@latest
openchrome check
openchrome info       # optional: print installed version and runtime details
```

No configuration changes are required to keep your existing v1.10.x
experience. The new pilot tier is opt-in via `--pilot`:

```json
{
  "mcpServers": {
    "openchrome": {
      "command": "openchrome",
      "args": ["serve", "--pilot"]
    }
  }
}
```

---

## Quick changelog

```
v1.10.4  →  v1.11.1   (cumulative, this release)

  138 files changed
  +24,209 / -210 lines
  72 first-parent commits
  27 PRs merged (PR #774 → PR #818)
  18 new tracking issues filed
  4 standalone fixes carried forward (#767 #771 #773 #742)
  Tier 1 MCP tool count:  34 → 38
  New MCP resources:       0 → 1   (openchrome://skill-graph/<domain>)
  New runtime deps:        0
  New dev deps:            1       (dependency-cruiser, CI-only)
  New native deps:         0       (argon2 remains the only one)
  npm overrides added:     2       (basic-ftp, ip-address — security)
```

---

## Breaking changes

**No source-code-level breaking changes for tool callers.** A v1.10.x
`~/.claude/.mcp.json` configuration works bit-identically with v1.11.1
when `--pilot` is unset.

The following are the **only** observable behavioral changes a v1.10.x
user can hit. Each one has a documented migration path.

### 1. Default headed Chrome auto-launch window placement

| | v1.10.4 | v1.11.1 |
|---|---|---|
| Default flags | `--start-maximized` + `--window-size=1920,1080` | `--window-position=0,0` + `--window-size=1280,900` |
| Window position | implicit (window-manager decided) | `0,0` |
| Window size | `1920×1080` | `1280×900` |
| Maximized? | yes, by default | no, unless requested |

**Why**: the v1.10.4 combination could place the window partly off-screen
on multi-monitor setups, scaled displays, or narrower work areas
(issue #770).

**Migration** if you depended on the old behavior:

```bash
# Keep maximize-on-launch
openchrome serve --auto-launch --start-maximized

# Keep 1920×1080 explicitly
openchrome serve --auto-launch --window-size 1920,1080

# Place on a specific spot (new in v1.11)
openchrome serve --auto-launch --window-bounds 1920,0,1280,900
```

Equivalent env vars are honored too: `OPENCHROME_WINDOW_SIZE`,
`OPENCHROME_WINDOW_POSITION`, `OPENCHROME_WINDOW_BOUNDS`,
`OPENCHROME_START_MAXIMIZED`. Invalid values produce a clear error
message and exit code `2`.

### 2. Tier 1 tool count bumped 34 → 38

v1.11.1 adds four new tools to the default Tier 1 surface:
`oc_assert`, `oc_evidence_bundle`, `oc_skill_record`, `oc_skill_recall`.

**Migration**: if your MCP client (or test harness) pins the Tier 1
tool count, bump the expected value from **34 → 38**. The four new
tools are additive; no existing tool is removed or renamed.

### 3. `oc trace` / SQLite removal (forward-looking)

The v1.11 line implements all new storage (trace, skill graph, skill
memory) on **JSONL or JSON files + `proper-lockfile`**, not SQLite.
v1.11.1 does **not** include the user-facing trace recorder hooks
yet — those land in a follow-up — so no existing trace-consumer code
breaks. If you have downstream tooling that reads `index.sqlite` from
a v1.10.x build, that file path is not produced by v1.11.x.

### 4. Pilot `--pilot` startup line on stderr

When `--pilot` is set, the server writes a one-line marker to
**stderr** at startup:

```
[harness] core+pilot enabled (trace,state_graph,contract_runtime,handoff_persist,perception_voting,skill_curator)
```

(Or `[harness] core only (--pilot not set)` when off.)

**Migration**: if your MCP client captures stderr and parses it for
specific patterns, this line is new. It is informational only — the
JSON-RPC protocol on stdout is unaffected (the line goes to stderr
explicitly because stdout carries the protocol).

### 5. Issue #721 closed without implementation (per P3)

The previously-proposed macOS Keychain + Windows Credential Manager
handoff persistence (#721) is **not implemented**. Per the new
portability-harness contract, OS keychain integration is forbidden
because it breaks the "anywhere-compatible" portability promise on
headless Linux environments without Secret Service.

**Migration**: if you were waiting on #721, use the new
`OPENCHROME_HANDOFF_KEY_FILE` opt-in for cross-restart handoff
persistence instead (see below).

---

## What ships in the v1.11 line

### Standalone fixes (carried forward from v1.10.5 / v1.11.0)

These four landed on `main` before the v1.11 cleanup work. They are
part of v1.11.1 by virtue of sitting between the previous npm publish
(v1.10.4) and this one.

| PR | Title | Effect |
|---|---|---|
| **#767** | `test: opt-in to allowUnauthenticatedHttp in HTTP transport tests` | Fixed four inherited HTTP-auth test failures on develop without weakening the v1.10.4 fail-closed production policy. The escape hatch (`allowUnauthenticatedHttp: true`) is the documented test-only path; production code is untouched. |
| **#771** | `Fix auto-launched Chrome window bounds placement` (closes #770) | New `--window-size` / `--window-position` / `--window-bounds` / `--start-maximized` CLI flags + matching `OPENCHROME_WINDOW_*` env vars. Default flipped from `--start-maximized` + `1920×1080` to `0,0` + `1280×900`. See breaking-change #1. |
| **#773** | `fix: improve javascript_tool diagnostics and Shadow DOM helpers for SPA scraping` (closes #772) | Adds `globalThis.__openchrome.querySelectorAllDeep(selector, root?)` and `globalThis.__pierce(selector)` page-context helpers for open shadow roots without monkey-patching `document.querySelectorAll`. Improved `Runtime.evaluate` diagnostics when CDP returns a Promise remote. Closed shadow roots remain out of reach. |
| **#742** | `fix(deps): bump basic-ftp and ip-address via npm overrides` (refs #688) | Top-level `npm overrides` for `basic-ftp@^5.3.1` and `ip-address@^10.1.1` to neutralize one critical and one moderate transitive advisory from the `@puppeteer/browsers → proxy-agent` chain (GHSA-5rq4-664w-9x2c, GHSA-6v7q-wjvx-w8wg, GHSA-rp42-5vxx-qpwr, GHSA-rpmf-866q-6p89, GHSA-v2v4-37r5-5v8g). `npm audit --omit=dev` now reports zero production vulnerabilities. |

### Portability-harness contract (PR #774)

- **`docs/roadmap/portability-harness-contract.md`** — durable contract; every future PR must satisfy it.
- **`docs/roadmap/history/openchrome-1.11-cleanup.md`** — one-time application plan; archived in this release.

The contract defines five principles:

- **P1. Tool server identity** — OpenChrome accepts tool calls and returns results; it does not orchestrate, run continuous loops, or manage agent lifecycles. Long-lived background work is permitted only in the pilot tier and only when it serves the tool-call guarantee.
- **P2. Zero-impact harness extension** — when `--pilot` is unset, an optional native dep fails to load, or the per-family flag is off, the v1.10.4 tool surface and behavior are preserved bit-identically.
- **P3. Anywhere-compatible MCP** — neither tier may require outbound LLM API calls (Anthropic, OpenAI, etc.), mandatory API keys at boot, platform-specific compile toolchains, OS keychains, or network-attached secret stores. Server-side LLM-driven decisions live in separate npm packages or host-side libraries.
- **P4. Facts versus decisions** — the server captures, computes, stores, retrieves, normalizes, and verifies facts. LLM judgment lives outside.
- **P5. Native dependency discipline** — `argon2` is the only mandatory native runtime dep. `better-sqlite3` is not adopted. Any future native dep ships in `optionalDependencies` with a documented fallback.

### `src/core/` ↔ `src/pilot/` tier split

```
src/
├── contracts/      # core contract DSL, evaluators, pHash, evidence helpers
├── core/           # P1–P5 strictly enforced; no flag; backward-compatible
│   ├── trace/
│   ├── skill/
│   ├── skill-memory/
│   └── perception/
├── harness/        # opt-in/flag plumbing and harness runtime helpers
├── tools/          # MCP tool registrations, including new core surfaces
└── pilot/          # opt-in via --pilot; P1/P4 relaxed; P3 still enforced
    ├── runtime/
    ├── handoff/
    ├── voting/
    └── curator/
```

Import direction is enforced unidirectionally by a `dependency-cruiser` rule: **`src/core/**` may not import from `src/pilot/**`**. The check runs on every PR (CI step `Lint tier boundary`, `npm run lint:tier`).

### `--pilot` CLI flag + per-family sub-flags

```bash
openchrome serve                    # default v1.10.x experience
openchrome serve --pilot            # opt into pilot
OPENCHROME_CONTRACT_RUNTIME=0 openchrome serve --pilot   # one family off
```

| Pilot family | Sub-flag (env) | Default inside `--pilot` |
|---|---|---|
| trace recorder | `OPENCHROME_TRACE` | active |
| skill state graph | `OPENCHROME_STATE_GRAPH` | active |
| contract runtime | `OPENCHROME_CONTRACT_RUNTIME` | active |
| handoff token + persistence | `OPENCHROME_HANDOFF_PERSIST` | active |
| voting framework | `OPENCHROME_PERCEPTION_VOTING` | active |
| skill curator | `OPENCHROME_SKILL_CURATOR` | active |
| skill curator structural merge (Pass 2) | `OPENCHROME_SKILL_MEM_MERGE` | inactive (off-by-default; costlier) |
| handoff key file (cross-restart persistence) | `OPENCHROME_HANDOFF_KEY_FILE=<path>` | unset (ephemeral default) |

When `--pilot` is unset, no module from `src/pilot/**` is loaded into the process (verified by a unit test that inspects the import resolver).

`src/harness/flags.ts` exposes `isPilotEnabled()`, six per-family getters, `activeFamilies()`, `logActiveFlags()`, and `bootstrapPilot()`. 32 unit tests cover argv/env parsing, cache behavior, family defaults and overrides, and the lazy-import guarantee.

### Core tier additions (unconditional, no flag)

Four new MCP tools and one new MCP resource template register unconditionally. Tier 1 tool count: **34 → 38**.

| Subsystem | Module path | New MCP surface | Tests |
|---|---|---|---|
| **Trace recorder** | `src/core/trace/{storage,redactor,types,index}.ts` — JSONL append-only storage (no SQLite), credential redactor (covering JWT / AWS keys / Bearer-Basic schemes / hex-32+ / SSN / URL credential params / sibling-form-field key+value pairs), filesystem-scan session index, per-domain `proper-lockfile` | (recorder CDP hooks land in a follow-up release) | 49 |
| **Skill state graph** | `src/core/skill/{storage,types,index}.ts` — JSON-per-domain storage with full schema (`nodes`, `edges` with `to_state_distribution`), per-domain lockfile coordination, IPv6-safe filename encoding, Windows reserved device-name prefix handling | `openchrome://skill-graph/<domain>` MCP resource (read-only) | 33 + 13 |
| **Skill memory** | `src/core/skill-memory/{store,stats-resolver,types,index}.ts` — JSON-per-domain skill record store, gzipped frozen snapshots, audit-log-backed `SkillStatsResolver` (streams `~/.openchrome/audit.log` in 64 KB chunks, lazy `AuditIndex` keyed strictly by `skill_id`) | `oc_skill_record`, `oc_skill_recall` MCP tools | 28 + 24 + 11 |
| **Outcome contracts** | `src/contracts/{evaluate,eval-context,validator,types,phash,screenshot-class,png-decode,safe-regex,index}.ts` + `src/contracts/evaluators/{dom-count,dom-text,logical,network,no-dialog,screenshot-class,url}.ts` — DCT-II perceptual hash, screenshot-class registry, zero-dependency PNG decoder | `oc_assert` (single-call verifiable state check), `oc_evidence_bundle` (DOM + screenshot + network + console + pHash bundle generator) MCP tools | 15 + 20 |
| **Perception primitives** | `src/core/perception/{metadata,cache,cross-check,image-features,types,index}.ts` — perceptual DOM metadata with in-process mtime cache, Sobel edge density, dominant-color (16-bin histogram), sRGB color distance | (consumed by `oc_evidence_bundle` and the pilot voting framework) | 31 + 23 |

#### `oc_assert` — single-call verifiable state check

```ts
oc_assert({
  contract: <Contract object>,
  args?: Record<string, unknown>,
  evidence?: { snapshot?: unknown },
}) -> {
  verdict: 'pass' | 'fail' | 'inconclusive',
  failed_assertions?: Array<{ name, expected, actual, location? }>,
  evidence_handle?: string,
}
```

Single call, single return. No retry. No escalation. (Pilot runtime provides those when `--pilot` is set.)

#### `oc_evidence_bundle` — on-demand state snapshot

```ts
oc_evidence_bundle({
  include?: ('dom' | 'screenshot' | 'network' | 'console' | 'phash')[],
  network_window_ms?: number,
  evidence?: { snapshot?: unknown },
}) -> { bundle_id, path, size_bytes, parts: string[] }
```

Default `include = ['dom', 'screenshot']`, `network_window_ms = 5000`, console cap 200 entries. Output is a flat directory of named parts (no ZIP — keeps the install dependency-free).

#### `oc_skill_record` and `oc_skill_recall`

```ts
oc_skill_record({ domain, name, steps, contract_id, frozen_snapshot? })
  -> { skill_id, stored_at, snapshot_path? }

oc_skill_recall({ domain, contract_id?, limit? })
  -> { skills: SkillRecord[] }
```

Both are read-only / explicit-write over the JSON skill-memory store. No LLM scoring (the pilot curator does ranking when `--pilot` is set; core is deterministic last-used-at order).

#### `openchrome://skill-graph/<domain>` MCP resource

Read-only JSON snapshot of the per-domain skill graph at request time. In-process mtime cache invalidates when the underlying JSON file changes on disk. URL-encoded domain segment so IPv6 and other unusual hosts round-trip safely.

### Pilot tier (opt-in via `--pilot`)

All pilot modules load lazily through `bootstrapPilot()` only when `--pilot` is set. Each family is independently gated by a per-family sub-flag; the default inside pilot is *active* (except structural merge — off by default since it's costlier).

| Family | Module | Sub-flag | Tests |
|---|---|---|---|
| **Contract runtime** — verdict taxonomy (pass / fail / inconclusive / validation_error / execution_error / budget_exhausted / escalated / aborted_by_hook), exponential-backoff retry, always-settles guarantee, abort handling, async audit emission with `.unref()`'d timers | `src/pilot/runtime/{runtime,types,index}.ts` | `OPENCHROME_CONTRACT_RUNTIME` | 64 |
| **Idempotency cache + preemptive cancellation** — SHA-256-keyed in-process cache with TTL, canonical JSON stringify so object property order doesn't matter, in-flight registry seeded for epoch-based supersession | `src/pilot/runtime/idempotency.ts` | (same flag as runtime) | 25 |
| **`beforeIrreversibleAction` hook** — fires only for `critical: true` contracts. Operator-registered hook can return `{ proceed: true }`, `{ proceed: false, reason }`, or `{ proceed: 'await-human', externalToken }`. Hook throws are treated as implicit deny. Default registered hook is a no-op pass-through (preserves v1.10.4 behavior). | `src/pilot/runtime/before-irreversible.ts` | (same flag as runtime) | 16 |
| **Handoff token + manager** — base64url-encoded 32-byte tokens (`node:crypto.randomBytes`), dual-index `(bySession, byToken)` for O(1) redeem, TTL-based expiry with `setInterval`+`.unref()` background prune. | `src/pilot/handoff/{token,banner,manager,index}.ts` | `OPENCHROME_HANDOFF_PERSIST` | 15 |
| **Handoff persistence (AES-256-GCM)** — ephemeral key by default (regenerated at boot; persisted tokens invalidated on restart). Opt-in cross-restart persistence via `OPENCHROME_HANDOFF_KEY_FILE=<path>` to a 32-byte key file. Wrong key size on the file: graceful fallback to ephemeral. No OS keychain integration. | `src/pilot/handoff/persistence.ts` | (same flag as token) | 19 |
| **Multi-model voting framework** — voter-agnostic `Voter` interface, `argsEquivalence()` action-arg canonicalization (whitespace, key order, semantic-equivalent selectors), `vote()` orchestrator with bounded concurrency, per-voter deadlines, one retry per voter. Ships with deterministic test voters. **No Anthropic/OpenAI HTTP wrappers** — those live in the future `openchrome-perception-voters` package (#775). | `src/pilot/voting/{orchestrator,args-equivalence,index}.ts` | `OPENCHROME_PERCEPTION_VOTING` | 57 |
| **Verified skill extractor** — turns successful, contract-verified runs into reusable SKILL.md candidates. Idempotent on `(graph_node_anchor, contract_id)`. Atomic writes, per-skill directory lock, sidecar JSON recovery. Prototype-pollution-hardened SKILL.md frontmatter parser. **No LLM calls** — pure deterministic transform. | `src/pilot/curator/{extractor,skill-md,types,index}.ts` | `OPENCHROME_SKILL_CURATOR` | 56 |
| **Skill recall ranking** — read-only over the core-tier `SkillMemoryStore`. Rank by `successCount DESC, lastUsedAt DESC, skillId ASC`. Byte-cap drop policy (8 KB default) for LLM-friendly payload sizes. Frozen-snapshot memoization. | `src/pilot/curator/recall.ts` | (same flag as curator) | 22 |
| **Curator Pass 1 (prune)** — two sub-passes: confidence-floor demote/archive (fail_rate > 30%, ≥5 runs, double-demote → archive) and TTL archival (stale 30 d, untouched 60 d). Moves to `.archive/<skill_id>/` + `reason.json`. Never deletes; idempotent. | `src/pilot/curator/prune.ts` | (same flag as curator) | (in shared 38-test suite) |
| **Curator Pass 3 (promote)** — async best-effort. Syncs sidecar `runs.count` and latest success timestamp into `SkillMemoryStore.markUsed`. Skips archived and store-unknown skills. | `src/pilot/curator/promote.ts` | (same flag as curator) | (in shared suite) |
| **Curator Pass 2 (structural sibling merge)** — detects `(domain, intent)` pairs whose step lists are structurally near-duplicate after URL normalization, action-arg canonicalization, optional-step pruning. Aggregates `success_count`, unions `runs.recent` oldest-first. **Structural-only, no LLM**. Gated by `OPENCHROME_SKILL_MEM_MERGE=1` (separate from main curator flag because merge is costlier). | `src/pilot/curator/merge.ts` + `src/pilot/curator/stop-words.ts` | `OPENCHROME_SKILL_CURATOR` + `OPENCHROME_SKILL_MEM_MERGE` | 27 |
| **PID-file lock** — atomic temp+`link` write, TTL-based stale reclamation (1 h default), `process.kill(pid, 0)` liveness probe. Prevents two curators against the same domain even across two `openchrome serve` processes. | `src/pilot/curator/lock.ts` | (same flag as curator) | (in shared suite) |
| **Background runner** — `setInterval` with `.unref()` so it doesn't pin the event loop. Acquires `CuratorLock` per cycle (skips silently if contested), runs Pass 1 → Pass 3 (Pass 2 if enabled). | `src/pilot/curator/runner.ts` | (same flag as curator) | (in shared suite) |

### Out of scope (per P3)

Two pieces from the original epic plans were redirected to separate packages rather than landing here, because their server-side LLM API calls would violate the "anywhere-compatible" portability promise:

- **LLM voting providers** (Anthropic + OpenAI HTTP wrappers) — tracked in **#775** as the future `openchrome-perception-voters` package.
- **LLM merge requester for the skill curator** — tracked in **#776** as the future `openchrome-skill-curator-llm` package.

The corresponding `Voter` and `MergeRequester` interfaces remain in `openchrome-mcp` so those separate packages can plug in cleanly when they arrive.

---

## Boundary plumbing (PR #787, #788, #789)

Three boundary-enforcement PRs landed before any tier-routed work:

- **#787** — `dependency-cruiser` config (`.dependency-cruiser.cjs`) with the `core-must-not-import-pilot` rule (severity: `error`). `src/core/index.ts` and `src/pilot/index.ts` placeholders with tier documentation. New `npm run lint:tier` script.
- **#788** — `lint:tier` wired into `.github/workflows/ci.yml` after the existing eslint step. `tests/core/` and `tests/pilot/` scaffolding with READMEs.
- **#789** — `--pilot` CLI flag plumbing on `serve`; `src/harness/flags.ts` with 32 unit tests covering all activation scenarios.

---

## Storage policy and dependency footprint

`better-sqlite3` is **not adopted**. All storage layers added in the v1.11 line use JSONL or JSON files plus `proper-lockfile` via the existing `src/utils/atomic-file.ts` helpers (`writeFileAtomicSafe`, `readFileSafe`, `acquireLock`).

The only mandatory native runtime dependency in `openchrome-mcp` is `argon2`. One new devDependency added for CI lint: `dependency-cruiser@^16`. Runtime dependency list is unchanged from v1.10.4:

```
argon2 ^0.43.0
commander ^12.0.0
jose ^5.10.0
proper-lockfile ^4.1.2
puppeteer-core (rebrowser-puppeteer-core 23.10.3)
uuid ^9.0.0
write-file-atomic ^5.0.1
```

Top-level `npm overrides` carried forward from v1.10.4 (PR #742):

```json
"overrides": {
  "basic-ftp": "^5.3.1",
  "ip-address": "^10.1.1"
}
```

These zero the `npm audit --omit=dev` production vulnerability count inherited from the `@puppeteer/browsers → proxy-agent` chain.

---

## Reliability and security carried forward

The v1.10.4 hardening continues unchanged in v1.11.1:

- HTTP transport fail-closed policy (unauthenticated boots rejected unless `allowUnauthenticatedHttp: true` is explicitly set)
- Dashboard REST authz with tenant ownership checks
- HTTP JSON-RPC batch concurrency bound + RequestQueue timeout clearing
- File upload root restriction (no arbitrary local paths)
- Screenshot + vision capture payload guards
- Same-origin CORS bypass scheme matching + DNS-rebinding defense

New in the v1.11 line:

- Handoff persistence uses AES-256-GCM with random 12-byte IV per record and 16-byte auth tag. Token filenames are SHA-256 hashed so original tokens never leak via the filesystem.
- Audit logging is unchanged; `SkillStatsResolver` walks the audit log in 64 KB chunks with a `StringDecoder`-backed iterator and builds a lazy `AuditIndex` keyed strictly by `skill_id` (prevents sibling contamination).

---

## Cleanup outcomes (the v1.11 backlog burn)

The v1.11 cycle burned through a 35-PR backlog accumulated against four in-flight epics:

- **35 originally-open PRs** all reached terminal state.
- **27 new PRs** merged to `develop` and `main` during the cleanup (PR #774 through PR #817 plus this release PR #818).
- **18 new tracking issues** filed. 14 closed by their replacement PRs; 4 remain open for follow-on work (#775 perception voters package, #776 skill curator LLM package, #778 native-deps.md trigger deferred, #779 stale `pr/*` branch cleanup chore).
- **Three SQLite-backed PRs** were rewritten as JSONL/JSON + `proper-lockfile` per principle P5.
- **Two PRs were redirected** to separate-package extraction (#760 → #775, #765 → #776).
- **Zero abandoned work**: every closed PR's branch is preserved on the remote (chore #779 will clean up post-release).

Detailed per-PR execution log lives at `docs/roadmap/history/openchrome-1.11-cleanup.md`. The durable contract remains at `docs/roadmap/portability-harness-contract.md`.

---

## Verification

- `npm run build`: clean (both `tsconfig.cli.json` and `tsconfig.json`)
- `npm run lint`: clean
- `npm run lint:tier`: 0 violations (~310 modules, ~625 dependencies cruised)
- Full Jest suite: passes on `main`
- CI matrix on the release-merge PR: 18/18 green (9 OS×Node combos × 2 base targets)
- Manual: `openchrome serve` (no `--pilot`) returns the v1.10.4 surface plus the 4 new core tools; `openchrome serve --pilot` registers pilot tools and prints `[harness] core+pilot enabled` line on stderr

---

## References

- Contract: `docs/roadmap/portability-harness-contract.md` (PR #774)
- Application plan (archived): `docs/roadmap/history/openchrome-1.11-cleanup.md`
- v1.11.0 release page: https://github.com/shaun0927/openchrome/releases/tag/v1.11.0
- Restructure-proposal hybrid resolution: comment thread on issue #768
- Meta tracker (closed): https://github.com/shaun0927/openchrome/issues/780
- Release plan (closed): https://github.com/shaun0927/openchrome/issues/786
