# Knocking: asking to be let in

<!-- [doc->REQ-CLI-SURFACE-SECTION-SITED] --><!-- [doc->REQ-KNOCK-VERBS] --><!-- [doc->REQ-KNOCK-FLOOD] --><!-- [doc->REQ-KNOCK-INBOX-DAEMON-OWNED] --><!-- [doc->REQ-KNOCK-KNOCKER-STAMP] -->

A **knock** asks an endpoint to let you reach it. It is a request, never a
grant: answering it is what writes an access rule, and the answer belongs to
the endpoint being asked.

<!-- [doc->REQ-KNOCK-BARE-VERB] -->

```text
spt knock wanda --surfaces MSG --send-only   # ask
spt knock list --for wanda               # see what is waiting for you
spt knock approve k-4f2a --approve-requested
spt knock deny k-4f2a
```

<!-- [doc->REQ-KNOCK-DEFAULT-SURFACE-MSG] -->

**A knock asks for `MSG` by default** — only use `--surfaces` if you need more
than MSG. The commonest knock is "let me talk to you", and asking for every
surface would put a whole-machine grant in front of someone who only meant to
answer a message. `--surfaces ALL` still asks for everything, out loud.

`spt knock <target>` **is** `spt knock send <target>` — the same command by a
shorter name, with the same flags, the same defaults and the same knocker. Use
whichever reads better.

The one place they differ is a name collision: a **subcommand always wins over
the target**, so an endpoint whose id happens to be `send`, `list`, `approve`,
`deny`, `new-code` or `redeem` cannot be reached by the bare form. Knock it as
`spt knock send <id>`.

Knocks work between machines as well as within one. If the endpoint you name
lives on another node, the knock travels there and lands in that node's inbox.

<!-- [doc->REQ-KNOCK-PRESCRIBED-REMEDY-HONORED] -->
**Surface names are case-insensitive**, and what you type is stored in the
table's own spelling: `msg`, `Msg` and `MSG` all become `MSG`, and `all` in any
case is the every-surface sentinel rather than a surface literally named "all".
You do not have to remember the vocabulary. Every verb whose invocation names a
surface prints a **`Control surfaces:`** section in its own `--help`, composed
from the surface table at render time — `spt knock` and its `send`, `approve`,
`new-code` and `list` forms; `spt endpoint access` (the listing and its
`allow`/`deny`/`remove`); `spt node access` and the same three; and
`spt subnet create`, whose open/closed choice is a posture over that whole
vocabulary. Each line names the traffic and what granting it **admits**: a
surface that carries a proven sender binds a grant to that single sender, and
one that does not admits the sender's whole machine. `spt knock redeem` and
`spt knock deny` print no such section on purpose — by then the surfaces are
already fixed by the code, or by the knock you are answering, so offering the
vocabulary there would suggest a choice you do not have.

A token that matches no known surface is **refused where you typed it**, naming
the surfaces that do exist — before the knock is sent, so a typo costs nothing
rather than travelling to another machine, waiting to be read, and being
approved into a grant nobody asked for.

## Nobody is interrupted

A knock lands in a **queryable inbox** and is never pushed at the receiving
agent. Somebody has to look — you, or another agent asking on your behalf.
That is deliberate: an unsolicited request that could interrupt an agent's
work would be a channel worth spamming.

The inbox is owned by the daemon, not by a perch, so a knock survives the
target's agent being asleep, detached, or absent entirely. Purging the
*knocker's* endpoint does not invalidate a knock they already sent.

Two things do notify, and only these two, because each is invited by something
you did yourself — each is an **answer to a request you sent**:

- an approval notifies the knocker — they asked;
- a redemption notifies the code's minter — they minted it.

A **knock-back** does not notify. It is a new request from the other side, not
an answer to yours, and new requests land in the inbox like every other.

## Who you knock as

There is no `--from`. The knocker is classified at invocation and stamped by
the daemon:

- an **agent** knocks as its own endpoint, and an approval whitelists exactly
  that endpoint;
- a **person at a terminal** knocks as *the humans on their machine* — the
  request is `subject <node>, origin user`, deliberately narrower than
  admitting the whole machine, so the commonest approval admits people and not
  their agents.

`--for <endpoint>` knocks on behalf of an endpoint that lives on **your**
machine. Naming one that does not exist here is refused rather than sent.

## Two-way reach is offered, never imposed

<!-- [doc->REQ-KNOCK-DIRECTIONALITY-SENDER] -->

**You say which way reach runs when you ask for it**, and only then. Every
reach-*requesting* command — `spt knock <target>`, `spt knock send` and
`spt knock redeem` — requires **exactly one of `--send-only` or
`--send-receive`**. There is no default: a bare invocation refuses and names
both, and passing both refuses too, because they are opposite answers to the
same question.

The flag describes **your own side**, never theirs:

- `--send-only` asks to reach them, and deliberately not the reverse. It is the
  explicit decline, and it is **recorded**, so a decision to keep reach
  one-directional reads back later as a decision and not as a flag nobody typed.
- `--send-receive` also pre-authorizes the reverse **on your own side**. Nobody
  writes another endpoint's rules; your daemon writes it if and when they
  answer. It is consumed exactly once, and an unanswered knock leaves it
  unconsumed — a conditional intention, not a standing grant.

<!-- [doc->REQ-SEC-1] -->
**What `--send-only` does not bar: replies.** A reply needs no rule of its own,
on any endpoint. The access gate implements a stateful firewall: a message
correlated to the receiving endpoint's **own outbound** is admitted on that
correlation, before any rule or mode is consulted — and this holds even when
the access store cannot be read, because the exemption precedes the store. The
engine room is the strictest consumer of the same rule, not its owner: even the
seat that refuses all inbound still answers replies to its own outbound. So a
`--send-only` grant means the far side cannot *initiate* traffic toward you;
it can always *answer* the traffic you start. Declining reverse reach declines
unsolicited sends, never the conversation you open.

**Answering carries no directionality at all.** `spt knock approve` and
`spt knock new-code` take no directionality flag, because accepting *is* your
own side's act — there is nothing further to declare. If you approve someone and
want to reach *them* as well, **knock back**:

```sh
spt knock send flynn --surfaces MSG --send-only
```

That is the same verb anybody else uses to ask for reach, which is the point:
one instrument, one grammar.

> **Renamed in v0.54.0.** `--mutual` is now `--send-receive` and `--one-way` is
> now `--send-only`, and both moved from the answering verbs to the asking ones.
> The old spellings are **parse errors** that name their replacement — there are
> no aliases. Pre-authorizations you armed under the old flags are untouched and
> still honored.

**Two-way reach works across machines.** A pre-authorization is still consumed only
on the node that armed it — nobody writes another endpoint's rules — and what
crosses is the answer, not the rule:

- an approval or denial travels back to the knocker's node as an **answer
  receipt**, and their own daemon writes the reverse rule (approval) or retires
  the intention (denial). A receipt acts only on a record bound at knock time to
  the node being knocked, so nothing else on the network can fire or destroy it;
- a **knock-back** is an ordinary knock and rides the wire any first-order knock
  rides, into the other side's inbox on their own machine.

If the far machine never answers, nothing opens and nothing closes: the
pre-authorization stays armed and expires with the knock, and every surface says
so rather than claiming an outcome.

## Invite codes

A code is a **pre-approval** you can hand to someone, with the granted surfaces
baked in at mint. Presenting it reaches your endpoint even if it is
undiscoverable, and auto-approves with exactly those surfaces.

```text
spt knock new-code --surfaces MSG          # mint (from that endpoint's session)
spt knock redeem <code> --send-only        # present one you were given
```

**A code carries its target.** Possessing one tells you nothing about any other
endpoint, so a code can never be used to enumerate what exists on a node.

Codes are single-use and expire. A redemption whose grant fails to write does
**not** spend the code — repair the problem and redeem the same code again.

## Redeeming from another machine

A code can be presented from any machine in the subnet it was sealed to, not
only from the one that minted it. You do not have to be able to see the minting
machine first, and you never name it yourself:

```text
spt knock redeem sptkc_...
```

**A sealed code carries its own route.** Sealed inside the `sptkc_` string,
alongside the secret, is the machine that minted it. Opening the seal takes a
key only members of that subnet hold — so opening it is itself the proof that
you belong there — and what comes out names both the subnet to look in and the
machine to carry the redemption to. Your machine then finds that minter among
the subnet members it already knows of.

It never looks the *endpoint* up, and that is the point: the endpoint you are
redeeming against is usually one that cannot be looked up at all. Being
unreachable by discovery is the ordinary reason somebody hands you a code in
the first place, so a redemption that depended on discovery would fail exactly
where it is needed.

Codes minted before sealed codes existed are plain strings with no route inside
them. They still work, and they still redeem on the machine that minted them.

### What you can be told

| outcome | what it means |
|---|---|
| **redeemed** | the grant is written. The reply names the target, the surfaces you were granted, and whether reach is now one- or two-directional |
| **refused** | the code was seen and turned down. **One message covers every cause** — unknown, expired, already used, too many attempts — because saying *which* would let a stranger learn from refusals which codes exist |
| **unconfirmed** | **no answer came back at all** |
| **peer silent** | **the machine took the redemption and then said nothing** — it is still holding the exchange open, which points at a machine that is wedged or overloaded rather than one too old to understand redemptions. Nothing was decided and **the code was not spent** <!-- [doc->REQ-WAN-REPLY-BOUND] --> |

The last two rows are the ones to remember: **silence is not a refusal.** The
minting machine may be off, unreachable, running a version old enough that it
does not understand redemptions, or wedged mid-exchange, and an answer can
simply be lost. Nothing was decided about your code and **it was not spent**, so
it is still worth presenting again. A presenter told "refused" here would throw
away a good code that was never even seen.

Two refusals arrive before anything is sent, and both are about the route
rather than the code:

- **it cannot be opened** — no subnet this machine belongs to holds a key that
  opens it. Either it was sealed to a subnet you are not in, or the string is
  damaged; nothing else about it can be told apart from here.
- **there is no route** — the code names a machine this subnet has not seen
  yet. That machine has to appear in the subnet before it can be reached.

A code whose route is ambiguous is refused rather than guessed at — presenting
a live code to the wrong machine is not a mistake worth risking to save a
prompt.

### Opening your own side, when you redeem

<!-- [doc->REQ-KNOCK-REDEEM-MUTUAL-RECEIPT] -->

`--send-receive` arms the reverse on **your own** side, against the code you
are presenting, and it does that whether the code was minted on this machine or on
another one. Nobody writes another endpoint's rules here either: what the flag
buys you is a rule your own daemon writes, and only once the far side has
actually let you in.

- **the code was minted on the machine you are redeeming from** — the arm is
  consumed in the same invocation, so reach is two-directional immediately.
- **the code was minted elsewhere** — the arm is recorded before the redemption
  is sent, and written only when the **redeemed** reply comes back node-proven.
  That reply is also what fills the rule in: the subject and the surfaces are
  the ones the answer carried, never a guess made here from a code that says
  nothing about who will answer it.

It is armed before the send rather than after because an intention written only
once the answer arrives could not survive an answer that never comes. So the
three outcomes in the table above are told apart on your own record and not
merely in the sentence printed beside them: a redemption consumes the arm, **a
refusal disarms it**, and **silence leaves it armed** — the same "silence is not
a refusal" rule the table keeps, applied to your side of the exchange.

## Limits that stop knocking being a nuisance

These are enforced by the **receiving** node, against its own store and clock —
a limit the sender honoured would only bind the honest:

| limit | value | why |
|---|---|---|
| one pending knock per (knocker, target) | — | re-knocking updates your request in place instead of queueing another, so an inbox cannot be filled by repetition |
| time to live | **24 hours** | an unanswered knock expires; the expiry is checked **when you answer**, so a stale listing can never make an expired knock approvable |
| inbound knocks per machine | ~10/hour | a flood from one node is refused before it reaches anyone's inbox |
| code redemption attempts | rate-limited per machine | guessing a code is bounded by this together with the TTL |

An expired knock reads as **expired**, not as though nobody ever asked.

## What an approval may and may not write

Approving names the surfaces you are granting — either `--approve-requested` to
grant what was asked, or `--surfaces` to grant less. **An approval can narrow a
request, never widen it.**

Which subject the grant binds depends on whether the surface can identify its
sender:

- on a surface whose traffic carries a **proven sender** (today `MSG`), the
  grant names exactly that endpoint;
- on a surface that carries none, the only subject that can match is the
  knocker's **whole machine**. Approving such a surface therefore admits every
  endpoint on it, whatever the knock named, so it requires `--admit-node` and
  is gated by the node's policy.

When a single knock mixes both kinds and the policy forbids the widening half,
the approval is **partial**: the identifiable half is granted, the rest stays
pending, and the output names the engine room as the seat that can answer it.
A partial approval is reported as partial — never as approved, and never as
denied.

## Imparting a note about whoever you admit

<!-- [doc->REQ-KNOCK-MONIC-IMPART] -->

An answer can also record what you have decided about the person it lets in —
a [monic](monics.md), which is what stops them arriving as a stranger:

```text
spt knock approve k-4f2a --approve-requested --monic "flynn's field-verify seat"
spt knock new-code --surfaces MSG --monic "whoever redeems this is a courier"
```

The code path is deferred by necessity, not by design: at mint there is nobody
to write a classification *about* yet, so a note typed at mint is imparted when
the code is **redeemed**, once the redeemer's identity is stamped and real.

**The note is about a daemon-stamped id, never a supplied one** — the same id
the grant is written for, so a note can no more be planted on an innocent peer
than a grant can. A knocker with no endpoint id — a bare-terminal human,
stamped as their node — **cannot** be classified: the note is refused loudly and
the grant stands, because a classification filed under a node id would be a
record no delivery ever matches while reading back as though somebody had been
classified.

**A note you already hold wins.** An approval must not silently rewrite what you
already decided about a peer, and a note typed at code-mint hours earlier
certainly must not. You are told which happened, and given the command to
replace it if that is what you meant. A record that is present but unreadable is
not a classification, so imparting replaces it.

**The note rides the grant and never changes it.** It is imparted only after the
grant has committed, is skipped entirely on a refusal, and a mind that cannot be
opened costs a loud diagnostic rather than a retracted approval.

A knocker admitted **without** a note still gets in — they simply arrive as an
unclassified peer, and their first message carries the
[trust warning](monics.md#the-trust-warning).

## Refusals you can predict

These exist so that a rule you can read back is a rule that actually governs.

**A rule about nobody is refused when you type it.** Every mutation names a
subject — `--endpoint`, `--node`, or `--any-of`. The error arrives before any
store is touched.

**A rule that could never match is refused rather than stored.** Naming a
sender endpoint on a surface that carries no proven sender is rejected at the
CLI: such a rule would sit in the view looking like protection while matching
nothing. Rule about the machine instead, or drop that surface.

**A rule naming no surfaces must satisfy the node policy on _every_ surface.**
Omitting `--surfaces` (or passing `ALL`, in any case) covers all of them, so if
any single surface is closed to that kind of grant, the whole blanket rule is
refused — otherwise the widest possible grant would be the easiest one to slip
past a closed posture.

## Where rules end up

Everything a knock produces is an ordinary access rule, visible in the usual
views with its origin recorded — `knock-approve` or `code-redeem` — and with
the exact command to remove it printed beside it. Rules born of a knock have no
special lifecycle afterwards; they are removed like any other.

See [Viewing access rules and posture](access-viewing.md).
