import io


def edit(path, pairs):
    src = io.open(path, encoding="utf-8").read()
    for old, new in pairs:
        assert src.count(old) == 1, (path, src.count(old), old[:70])
        src = src.replace(old, new, 1)
    io.open(path, "w", encoding="utf-8", newline="").write(src)


# ------------------------------------------------------------------ SUMMARY
edit(
    "docs-site/src/SUMMARY.md",
    [("  - [Cross-node serving](serving/cross-node.md)",
      "  - [Cross-node serving](serving/cross-node.md)\n  - [Attachments and `spt fetch`](serving/attachments.md)")],
)

# --------------------------------------------------------- serving overview
edit(
    "docs-site/src/serving/overview.md",
    [
        (
            "| `m/`, `bin/`, `install` | Reserved facets; currently answer 404 naming the facet |",
            "| `m/<short-id>` | One message, rendered; `?json` for its machine twin |\n"
            "| `bin/`, `install` | Reserved facets; currently answer 404 naming the facet |",
        ),
        (
            "<!-- [doc->REQ-WEB-ACCESS-SURFACE] -->\n## Access control",
            """<!-- [doc->REQ-WEB-ENTRY-AUDIENCE] -->
## Entry fields: lifetime, audience, origin

Beside its path and served name, an entry may carry three fields. They are
per-entry and independent of kind, and `spt serve list` renders each one it has
— the registry's job is answering what this node exposes **and to whom**, and a
narrowing that did not render would make that answer a half-truth.

| Field | Meaning |
|---|---|
| `ttl` | A lifetime from registration. Absent means the entry lives until removed. Attachments carry 30 days by default; entries the `FILE_ACCESS_HELPER` registers carry 24 hours. |
| `audience` | The one endpoint allowed to fetch it. Absent means anyone the `WEB` surface admits. |
| `origin` | The endpoint that registered it — for a helper-registered entry, the message short-ID it came from. |

An **audience** is a cross-node narrowing. The owner serves the entry only to
the named endpoint and answers everyone else `403` naming the surface —
deliberately the same refusal a `WEB` deny rule produces, so a narrower audience
cannot be distinguished from a denial by probing and the registry does not
become an oracle for what exists.

It is enforced where the fetch origin is **proven**: the cross-node stream's
handshake identity. That handshake proves a *node*, while an audience names an
*endpoint*, so the check the owner can honestly make is whether the proven node
hosts that endpoint — it stops another machine, not another agent on the
audience's own machine. An audience this node cannot place is refused rather
than admitted.

**Loopback remains the trusted machine.** A local browser presents no endpoint
identity, so a request arriving on the loopback listener is served regardless of
audience. An operator who needs secrecy from other users of the same machine has
file permissions, not this field.

An entry whose lifetime has elapsed stops serving immediately — it answers the
ordinary not-found — whether or not the daemon's reaper has run yet.

<!-- [doc->REQ-WEB-ACCESS-SURFACE] -->
## Access control""",
        ),
    ],
)

# ------------------------------------------------------- messaging overview
edit(
    "docs-site/src/messaging/overview.md",
    [
        (
            "## Addressing\n\nBare ids (`sergey`) resolve locally first",
            """<!-- [doc->REQ-MSG-SHORT-ID] -->
## Message short-IDs and replies

Every message gets an eight-character short-ID when it is committed, minted from
the message's own content hash and scoped to the node that holds it. Nothing
mints a second identifier later: the same token appears in the delivery envelope
(`msg-id`), in the `MSG_IN` and `MSG_OUT` io-event rows, and in the URL
`/<node>/m/<short-id>` — so what you say in a chat line is what you click, and
two readers cannot disagree about what a message is called.

The alphabet is base32 without `0`, `1` and `8`, so `O`/`0` and `I`/`1` are
never two spellings of one id. Read one back with:

```console
$ spt msg show BCDFGH23
BCDFGH23 from doyle to todlando at 1788750167374ms [spool]
attachment: report.md http://localhost:5474/kitsubito/f/report.md (2481 bytes)
---
the report you asked for
```

`--json` is the machine twin, and `/<node>/m/<id>` renders the same message in a
browser (`?json` there too). Treat the id as **opaque**: a collision on one node
is detected when it is minted and resolved by lengthening that one id to nine
characters, then ten, which is safe precisely because no reader parses it.

`spt send <target> --reply-to <id>` carries the parent's id in the envelope so
an adapter *may* render a thread. It is a label on the message, not a way to
address one: `target` stays required, and an **unknown parent is carried, not
refused** — the parent may live on a node we cannot ask, and refusing would make
a thread across a partition impossible rather than merely unrendered.

Files ride along the same way: see
[Attachments and `spt fetch`](../serving/attachments.md).

## Addressing

Bare ids (`sergey`) resolve locally first""",
        ),
        (
            "`send` · `ring` · `ready` (blocks; `--once` drains and exits) · `list` ·\n`stop` · `whoami`",
            "`send` · `ring` · `ready` (blocks; `--once` drains and exits) · `list` ·\n`stop` · `whoami` · `msg show` · `fetch`",
        ),
    ],
)

# ---------------------------------------------------------------- frames.md
edit(
    "docs-site/src/shells/frames.md",
    [
        (
            """Two categories are named but **not built** — `PROJECTS` and
`FILE_ACCESS_HELPER`. They are deferred deliberately, tracked as their own
issues, and naming them in a spec is harmless: unknown names are ignored.""",
            """One category is named but **not built** — `PROJECTS`. It is deferred
deliberately, tracked as its own issue, and naming it in a spec is harmless:
unknown names are ignored. `FILE_ACCESS_HELPER` was deferred in the same way and
is now built; it appears among the categories added after v1 below.""",
        ),
        (
            "| `SEAL_BRIEF` | What sealing is and how to prove one — two sentences, once per session. |",
            "| `SEAL_BRIEF` | What sealing is and how to prove one — two sentences, once per session. |\n"
            "| `FILE_ACCESS_HELPER` | The exact `spt fetch` line for a file you were handed — an attachment on a delivered message, or a filepath a user quoted at you. |",
        ),
    ],
)

print("docs ok")
