# 0055 — Knock directionality is sender-declared

Status: accepted (2026-08-04 bag grill, releases#144)

## Context

Reach between endpoints is armed self-sovereignly: each side's rules admit inbound traffic, and
no verb writes the other side's rules. "Mutual" reach therefore decomposes into two sovereign
acts — the requester opening their own inbound to the counterparty, and the counterparty's
grant admitting the requester.

As shipped, the directionality choice was **mandatorily** demanded at the *receiver* verbs
(`knock approve`, `knock new-code` — mutex `--mutual`/`--one-way`, no default), while the
*requesting* verbs (`knock`, `knock redeem`) carried an **optional** `--mutual` with a silent
one-way default. `approve --mutual` acted as a *counter-knock* — a rider on the approval asking
the knocker to open their side.

The operator's design intent (releases#144) was the inverse: the requester must always declare
which way reach runs; the receiver's accepting act carries no directionality at all.

## Decision

Directionality is **sender-declared, and only sender-declared**.

- The reach-*requesting* surfaces (`spt knock`, `spt knock redeem`) take a **mandatory,
  mutually exclusive** pair: `--send-only` | `--send-receive`. No default; a bare invocation
  refuses. The declaration names what the **declarer's own side** does — `--send-receive` arms
  the declarer's own inbound to the counterparty (on approval / at redemption), nothing more.
- The receiver verbs (`knock approve`, `knock new-code`) carry **no directionality flag**.
  Accepting *is* the receiver's own-side act; there is nothing further to declare.
- The `approve --mutual` counter-knock is **removed, not renamed**: a receiver who wants
  reverse reach **knocks back**. The counter-ask is the knock verb's own job; a rider on
  approval duplicated it with a second grammar.
- Vocabulary: **send-only** / **send-receive** are canonical; "mutual"/"one-way" are retired
  ("mutual" misdescribes an act that only ever opens the declarer's side).
- Migration is a **clean break**: the old flags are parse errors pointing at the new ones. No
  deprecation aliases (they would keep the retired vocabulary alive in scripts and agent
  habits). Internal store/wire field names and already-armed pre-authorizations are untouched
  and stay honored — the rename is the user-visible surface only.

## Alternatives rejected

- **Every surface decides (B)**: keep the receiver-side mandatory mutex *and* add the
  sender-side one. Preserves the counter-knock, but makes the same flag mean different things
  at different verbs (own-side declaration at senders, counter-ask at approve) and doubles the
  mandatory-flag surfaces. Rejected: one meaning, one seat.
- **Sender-only plus a renamed optional counter-ask on approve**: rejected as the same
  duplication with a politer name.
- **Deprecation aliases**: rejected; fleet is small and the point of the rename is to retire
  the old mental model.

## Consequences

- Every reach request records an explicit directionality decision; the silent one-way default
  at requesting surfaces is gone (the shipped code already argued this for approve — the
  argument was right, the seat was wrong).
- A knock arriving with `--send-receive` tells the approver the requester's side is already
  committed; approval alone completes two-way reach.
- Scripts invoking bare `spt knock`/`redeem` break loudly at upgrade and state the fix.
- CONTEXT.md "knock / knock-code" entry carries the ratified vocabulary.
