2026-09-23·by Sijie Wang#software

facade-parity

Parent: structure · Kin: facade-directions · owner-facade-from-registry · ui-mcp-parity

One registry; every facade is generated from it or verified against it. Never a second hand-written description of the same capability.

The problem it answers

The product exposes the same underlying capability through several surfaces: the admin UI's HTTP routes, the owner's MCP tools, the API-key facade, the visitor agent's toolset. Written independently, they drift — and drift here is not a cosmetic difference. An op that exists on the admin panel and not on MCP means the owner can do a thing by clicking and cannot ask their AI to do it, which is precisely the product's premise failing quietly.

The gap was measured, not assumed: when the parity mechanism first ran it found 56 ops present on one plane and missing on the other. The estimate beforehand had been about fifteen. That ratio is the argument — a discrepancy nobody could see by reading was almost four times what the people who wrote the code guessed.

The mechanism

A domain declares each op as data, once:

fp.Op{ ID, Description, InputSchema, Kind, Reach, Invoke }
  • Kind — Read / Query / Action, which is what a facade renders into its own idiom (HTTP GET vs QUERY vs POST).
  • Reach — the exposure intent, declared against facade classes, never facade names: OwnerRead(), OwnerAction(), OutwardRead(), OutwardAction(), or Only(reason, …) for a genuinely single-surface op — where the reason is mandatory, so a gap is an explicit reviewed decision rather than a silent omission.

Because reach is declared against classes, a facade added later binds automatically. Nobody walks the op list again.

The dispatcher aggregates all declarations into the single registry (convergence-inbound-and-outbound); paritymanifest records what each facade class may carry; a ratchet holds the remaining unmigrated files and may only shrink.

Why declare-once beats audit-later

Parity as an audit is a task someone must remember to run and must run completely. Parity as a projection is a property you cannot violate: there is no second place to write the description down, so there is no second description to disagree.

The residual risk is not drift but coverage: an op that was never declared is invisible to the mechanism. That is exactly how corpus_graph slipped — a route with no MCP twin, not registered in the hand-written cross-reference table either, so the ratchet never saw it. A registry only guarantees agreement among the things inside it.

What it does not do

It does not decide whether an op should exist on a plane — Reach is a human judgement, and Only(reason, …) exists so that judgement is written down next to the op instead of inferred from its absence. Parity guarantees that the answer is stated once and honoured everywhere.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →