import io


def load(p):
    raw = io.open(p, encoding='utf-8', newline='').read()
    return raw.replace('\r\n', '\n'), '\r\n' in raw


def save(p, s, crlf):
    if crlf:
        s = s.replace('\n', '\r\n')
    io.open(p, 'w', encoding='utf-8', newline='').write(s)


def rep(s, old, new):
    assert s.count(old) == 1, (old[:90], s.count(old))
    return s.replace(old, new)


# ---- instances overview
p = 'docs-site/src/instances/overview.md'
s, c = load(p)
s = rep(s, '''| `spt endpoint wake`, or one of its shells' wake-watchers | active |
''', '''| `spt endpoint wake`, or one of its shells' wake-watchers | active |
| `spt send --handoff <id>@<node>` from its active sibling | active (see below) |
''')
s = rep(s, '''## Auto-suspend
''', '''## Handing attention to a sibling, and what a dormant instance may send

<!-- [doc->REQ-INSTANCE-HANDOFF] -->
<!-- [doc->REQ-DORMANT-SEND-RESTRICTION] -->

**A dormant instance may message only its active sibling.** Anything else it
tries to send is refused with `SEND_REFUSED_DORMANT`, and nothing is spooled:

- a message to another endpoint;
- a message to a dormant or suspended sibling;
- a `spt ring`;
- traffic to its own shells: `shell send`, `shell cmd`, `shell drive` and
  `shell tunnel`.

The refusal names the active sibling (`<id>@<node>`) and the way out. Either
message that sibling, or have it hand attention back with `--handoff`. When
no instance of the id is active, the refusal names `spt endpoint wake
<id>@<this node>` instead. The instance can also take active through your own
input at its controls.

Two sends stay open to a dormant instance:

- A **bare send to its own id** (`spt send ling` from `ling`) stays on that
  instance and is allowed. A bare self-id means "me", not a peer, and this is
  how a recharge wakes a session.
- A send to its **active sibling**, qualified with that sibling's node
  (`spt send ling@desk`).

**What the restriction does not cover:** `knock`, `fork`, `span`, `rc`, the
rest verbs (`wake`, `suspend`), `digest` and `notify` are not refused. The
restriction is about messages and shell traffic only.

The restriction keys on the **proven** sending instance: the session the send
runs in, or the author the `api state` gate proved for a shortform tag. It
never keys on a `--from` label. A send from a plain terminal proves no
instance, so it is not restricted. The restriction keeps a dormant instance
from speaking over the active one. It is not an access control.

**`--handoff` passes attention to a sibling.** It is a flag on an ordinary
send and carries a normal body:

```sh
echo "over to you" | spt send --handoff ling@desk
```

Only the **active** instance may send it, and the target must be the sender's
own id qualified with the sibling's node. A bare `--handoff ling` is refused
and lists the siblings to choose from.

| The sibling is | What happens |
|---|---|
| dormant | it takes active; the sender goes dormant |
| suspended | it wakes and takes active; the sender goes dormant |
| offline | refused before anything is sent; the sender stays active |

The **receiving** instance takes active when the message arrives. The sender
goes dormant when that activation reaches its node, the same way any
instance learns a sibling became active. So the sender never goes dormant
unless the sibling really took active: if a suspended sibling fails to
resume, the sender stays active. For about one registry push, both
instances can read active at once.

The active instance's normal message to a sibling moves nothing. A suspended
sibling wakes dormant, as in the trigger table above.

What `--handoff` prints:

| Line | Meaning |
|---|---|
| `HANDOFF:<id>@<node> — it takes active …` | The sibling answered that it took active. Exit 0. |
| `HANDOFF_REFUSED:<target> — <why>` | Refused before anything was sent (a sender that is not active, a bare or foreign target, an offline or unknown sibling), or the receiver denied the message. The sender stays active. |
| `HANDOFF_NOT_TAKEN:<target> — …` | The body was delivered as a normal message, but the receiver did not take active. It most likely runs an older spt that does not know the flag. The sender stays active. |
| `HANDOFF_UNCONFIRMED:<target> — …` | No answer came back. The sibling may have taken active; check `spt endpoint list`. |

## Auto-suspend
''')
save(p, s, c)

# ---- messaging overview failure table
p = 'docs-site/src/messaging/overview.md'
s, c = load(p)
s = rep(s, '''| `EMPTY_MSG` | Refused: empty body. |
''', '''| `SEND_REFUSED_DORMANT:<target> — <why>` | The sending instance is **dormant**, and a dormant instance may message only its active sibling. The line names that sibling (`<id>@<node>`) and the way out; nothing was spooled. See [Instances](../instances/overview.md). <!-- [doc->REQ-DORMANT-SEND-RESTRICTION] --> |
| `HANDOFF_REFUSED` / `HANDOFF_NOT_TAKEN` / `HANDOFF_UNCONFIRMED` | A `--handoff` send did not pass attention. See [Instances](../instances/overview.md) for each line. <!-- [doc->REQ-INSTANCE-HANDOFF] --> |
| `EMPTY_MSG` | Refused: empty body. |
''')
save(p, s, c)

# ---- CONTEXT.md
p = 'CONTEXT.md'
s, c = load(p)
s = rep(s, '''- A **dormant** instance may message **only its active sibling**. Everything else it would send — peer messages and traffic to its own shells — is refused with `SEND_REFUSED_DORMANT`, which names the active sibling and the `--handoff` way out. It can still become active through its own user input.
- **`--handoff`** is a flag on an ordinary send (it carries a body), and only the **active** instance may send it. To a dormant sibling it swaps the two. To a suspended sibling, the sibling wakes active and the sender goes dormant. To an offline sibling it is refused and the sender stays active, so active never lands on a seat that cannot take it.
''', '''- A **dormant** instance may message **only its active sibling**. Everything else it would send — peer messages and traffic to its own shells — is refused with `SEND_REFUSED_DORMANT`, which names the active sibling and the `--handoff` way out. It can still become active through its own user input. A **bare send to its own id** is not a peer message and stays allowed (R4-9 applied to the restriction; the recharge wake is such a send). With no active sibling (active vacancy), the refusal names `spt endpoint wake <id>@<this node>` as the way out. The restriction keys on the PROVEN sending instance (the session, or the author `api state` proved for a shortform tag), never a `--from` label, and a send that proves no instance is not restricted: it is attention discipline, not an access control. Covered: `send` (and shortform), `ring`, `shell send/cmd/drive/tunnel`. Not covered: knock, fork, span, rc, the rest verbs, digest, notify.
- **`--handoff`** is a flag on an ordinary send (it carries a body), and only the **active** instance may send it. To a dormant sibling it swaps the two. To a suspended sibling, the sibling wakes active and the sender goes dormant. To an offline sibling it is refused and the sender stays active, so active never lands on a seat that cannot take it. **The receiver applies it:** a same-id message carrying the flag is a wake at the receiving instance, which answers `handoff` when it holds active after admission. The sender never writes its own state. It goes dormant when the new activation outranks it, through the ordinary sibling-activated edge, so a sibling that never becomes active never demotes it.
''')
save(p, s, c)

# ---- REQ activation
p = 'traceable-reqs.toml'
s, c = load(p)
s = rep(s, '''title = "--handoff IS A FLAG ON AN ORDINARY SEND (releases#349): it carries a normal body and only the ACTIVE instance may send it. To a dormant sibling the two swap; to a suspended sibling the sibling wakes ACTIVE and the sender goes dormant; to an offline sibling it is REFUSED and the sender stays active, so active never lands on a seat that cannot take it. The active instance's normal message to a sibling causes no transition."
required_stages = []''', '''title = "--handoff IS A FLAG ON AN ORDINARY SEND (releases#349): it carries a normal body and only the ACTIVE instance may send it. To a dormant sibling the two swap; to a suspended sibling the sibling wakes ACTIVE and the sender goes dormant; to an offline sibling it is REFUSED and the sender stays active, so active never lands on a seat that cannot take it. The active instance's normal message to a sibling causes no transition."
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED INSTANCE-AXES W3 (todlando 2026-09-26, releases#349). Receiver-applied: WanMessage.handoff (serde default, skip if false) makes a same-id message a Wake at WAN admission; the receiver answers the reply token `handoff` when it holds active after admission; the sender never writes its own rest state and is demoted by the ordinary sibling-activated edge. N-1 receiver => HANDOFF_NOT_TAKEN.''')
s = rep(s, '''title = "A DORMANT INSTANCE MAY MESSAGE ONLY ITS ACTIVE SIBLING (releases#349). Peer messages AND traffic to its own shells are refused with SEND_REFUSED_DORMANT, which names the active sibling (id@node) and the --handoff way out. The refusal is loud and names the way out; it never silently drops. The instance can still become active through its own user input."
required_stages = []''', '''title = "A DORMANT INSTANCE MAY MESSAGE ONLY ITS ACTIVE SIBLING (releases#349). Peer messages AND traffic to its own shells are refused with SEND_REFUSED_DORMANT, which names the active sibling (id@node) and the --handoff way out. The refusal is loud and names the way out; it never silently drops. The instance can still become active through its own user input."
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED INSTANCE-AXES W3 (todlando 2026-09-26, releases#349). doyle ruling: a bare self-send is exempt (R4-9, Q1); surfaces send + shortform, ring, shell send/cmd/drive/tunnel (Q2); restricted during active vacancy with the wake way out (Q5); keyed on the PROVEN instance (session, or the api-state-gated shortform author, condition 1); no proven id is not restricted (condition 2).''')
save(p, s, c)
print('docs ok')
