"On one side of the seam, Firelink orchestrates. On the other, the sibling does the real work. The seam is where they never bleed into each other."
Where the Shrine Stops and the Flame Begins
Back in the first track, the rule was that Firelink orchestrates but never absorbs a sibling's implementation. The adapter is the precise line where that happens. When Firelink deploys a native app, it doesn't know how signing works — it invokes the deploy adapter, which fronts the tool that owns signing. When it fans out a repo tree, it invokes the tree adapter, which fronts the canonical sync tool. The adapter is the seam: Firelink's typed orchestration on one side, the sibling's owned implementation on the other, and a clean contract between them so neither reaches into the other.
An Adapter Declares a Fixed Contract
Every adapter declares a typed contract, not a loose function: a stable adapter id; the capability it supports; a canonical executable path; the member or target mapping; the required host role; the allowed peer ids; whether it supports dry-run; a timeout and an exclusivity class; and a result parser plus a postcondition verifier. That contract is what lets Firelink invoke real, dangerous work safely — it knows exactly what will run, where it's allowed to run, how long to wait, and how to confirm it worked, all without embedding the sibling's logic.
Only the Web Backend, Only From Office, Only Via a Plan
The invocation boundary is strict: the Native Launcher never calls adapters at all, and the Web backend is the only caller — invoking them only from office, only through a persisted plan. And the guardrails live in the adapter and registry, not as string checks sprinkled across routes: Mom's Mac is outside deploy scope, and Cinder stays office-only, as registry and adapter invariants. That's the payoff of centralizing the seam — a rule like 'never deploy to Mom's Mac' is declared once, in the adapter contract, instead of re-checked (and eventually forgotten) in a dozen scattered code paths.