import io, sys

P = "docs-site/src/harness-contract/echo-commune.md"
with io.open(P, "r", encoding="utf-8", newline="") as f:
    src = f.read()

NL = "\r\n" if "\r\n" in src else "\n"


def sub(old, new, label):
    global src
    n = src.count(old)
    if n != 1:
        sys.exit("ANCHOR %s matched %d times (need exactly 1)" % (label, n))
    src = src.replace(old, new, 1)


def L(*lines):
    return NL.join(lines)


# --------------------------------------------------------------------- intro
sub(
    L(
        "The **echo-commune** recovers it: spt-core runs the adapter's bounded",
        "summarizer over the session, captures the brief the summarizer prints, and",
        "files it as the session's context delta — the same delta a hand-written",
        "[commune](../lifecycle/overview.md) would have carried.",
    ),
    L(
        "The **echo-commune** recovers it: spt-core runs the adapter's bounded",
        "summarizer over the session, captures the brief the summarizer prints, and",
        "routes it straight into the session's durable context — the same delta a",
        "hand-written [commune](../lifecycle/overview.md) would have carried.",
    ),
    "intro-files-it",
)

sub(
    L(
        "you declare, the keys spt-core fills, what it does (and does not) feed the",
        "summarizer, how the summarizer locates the harness, and the drop-file",
        "protocol spt-core uses to file the result. It is the companion to the",
    ),
    L(
        "you declare, the keys spt-core fills, what it does (and does not) feed the",
        "summarizer, how the summarizer locates the harness, and where spt-core routes",
        "the result. It is the companion to the",
    ),
    "intro-protocol",
)

sub(
    L(
        "> The echo-commune is spt-core's. The adapter supplies **one command template",
        "> and one watched directory** — spt-core owns the spawn, the keys, the",
        "> file-drop, and the ingest. Everything below is the seam between those halves.",
    ),
    L(
        "> The echo-commune is spt-core's. The adapter supplies **one command",
        "> template** — spt-core owns the spawn, the keys, and the routing of the brief",
        "> into the durable tiers. Everything below is the seam between those halves.",
    ),
    "intro-blockquote",
)

sub(
    "<!-- [doc->REQ-DOC-ECHO-COMMUNE-CONTRACT] the full echo-commune I/O contract: the role + fields, the key catalog spt-core fills, the no-history-on-stdin rule, read-env self-locate, the single-writer/per-endpoint-resolver/ingest-deletes drop-file protocol, and stdout ingestion -->",
    L(
        "<!-- [doc->REQ-DOC-ECHO-COMMUNE-CONTRACT] the full echo-commune I/O contract: the role + fields, the key catalog spt-core fills, the no-history-on-stdin rule, read-env self-locate, stdout routed straight to the durable tiers, and the agent-owned commune_dir it never writes to -->",
        "<!-- [doc->REQ-ECHO-BRIEF-DIRECT-ROUTE] the brief routes from the summarizer into the two-tier store on the fire; commune_dir is the agent's own channel and the echo never writes it -->",
    ),
    "doc-tag",
)

# ------------------------------------------------------- bound-kill paragraph
sub(
    L(
        "the budget is a third outcome: the child dies *before* writing anything, so",
        "there is no drop to recover and no partial file to clean up — the presence of a",
        "file cannot be the signal, because nothing was ever written. spt-core therefore",
        "records the **expectation** before it spawns, and an expectation that no drop",
        "ever satisfies surfaces at the agent's next resume as a",
    ),
    L(
        "the budget is a third outcome: the child dies *before* printing anything, so",
        "there is nothing to route and no partial result to clean up — a brief that was",
        "never produced leaves no trace to detect it by. spt-core therefore",
        "records the **expectation** before it spawns, and an expectation that no brief",
        "ever satisfies surfaces at the agent's next resume as a",
    ),
    "bound-kill",
)

# ------------------------------------------------ the protocol section itself
OLD_HEAD = L(
    "## Drop-file protocol",
    "",
    "spt-core files the summarizer's output as a **drop file** — the exact same",
    "channel a hand-written commune uses — then ingests and deletes it. Three",
    "invariants define the contract:",
    "",
    "**1. spt-core is the single writer.** The filename is fixed by spt-core —",
    "`<endpoint_id>-commune.md` — and the adapter declares only the *directory*",
    "(`[session] commune_dir`). The mind never writes this file; spt-core writes it",
    "atomically (with a bounded access-denied-only retry for transient",
    "antivirus/indexer locks) and is the sole deleter. Never have the summarizer",
    "write the drop file itself.",
    "",
    "**2. The directory resolves per-endpoint** *(hardened in v0.29.0).* A",
    "`commune_dir` may be absolute or relative:",
    "",
    "- **Absolute** → used as-is.",
    "- **Relative** → resolved against the **endpoint's own recorded working",
    "  directory**, read fresh at drop time — never against the daemon's process",
    "  cwd.",
    "- **Relative with no recorded cwd** → spt-core **skips the drop and warns once**",
    "  (per endpoint, per daemon run). It never guesses and never falls back to the",
    "  daemon's cwd. This is the fix for a real outage: under a service-launched",
    "  daemon whose process cwd was a system directory, a relative drop dir once",
    "  resolved there and failed with a permission error on every write. The loud",
    "  skip makes a mis-declared relative dir a diagnosable signal instead of a",
    "  silent failure. Declare an **absolute** `commune_dir`, or ensure the",
    "  endpoint's cwd is recorded, to avoid the skip.",
    "",
    "A missing `[session.echo_commune]` role is likewise a **loud once-skip**, not a",
    "retried fault.",
)

NEW_HEAD = L(
    "## Where the brief goes",
    "",
    "spt-core routes the summarizer's output **straight into the durable context",
    "tiers** at the moment of the fire, stamped `Source: echo-commune`, and publishes",
    "it as a `COMMUNE` I/O event. The brief never becomes a file. Three invariants",
    "define the contract:",
    "",
    "**1. The echo routes; it does not file.** The brief goes from the summarizer's",
    "stdout into the two-tier store in one step, through the same routing the",
    "drop-file ingest uses. Nothing is written to disk on the way, so there is no",
    "window in which the brief sits as a file — for anything else to overwrite, or to",
    "be overwritten by.",
    "",
    "**2. `commune_dir` is the _agent's_ channel, not the echo's.** The directory an",
    "adapter declares under `[session] commune_dir` is where **the agent** drops its",
    "own hand-written commune — the fixed filename `<endpoint_id>-commune.md` — which",
    "spt-core then ingests and deletes. The echo does not write there.",
    "",
    "> *spt-core filed its echo brief to that same path until v0.67.x. Two writers on",
    "> one path with no arbitration meant an agent's boundary commune, authored at a",
    "> `/clear` and not yet ingested, could be overwritten **unread** by the echo that",
    "> fired seconds later. See KNOWN-HAZARDS 6.12.*",
    "",
    "A `commune_dir` may be absolute or relative, and the **ingest** resolves it",
    "per-endpoint *(hardened in v0.29.0)*:",
    "",
    "- **Absolute** → used as-is.",
    "- **Relative** → resolved against the **endpoint's own recorded working",
    "  directory**, read fresh — never against the daemon's process cwd.",
    "- **Relative with no recorded cwd** → spt-core **skips and warns once** (per",
    "  endpoint, per daemon run). It never guesses and never falls back to the",
    "  daemon's cwd. This is the fix for a real outage: under a service-launched",
    "  daemon whose process cwd was a system directory, a relative dir once resolved",
    "  there and failed with a permission error. The loud skip makes a mis-declared",
    "  relative dir a diagnosable signal instead of a silent failure. Declare an",
    "  **absolute** `commune_dir`, or ensure the endpoint's cwd is recorded, to avoid",
    "  the skip.",
    "",
    "An adapter that declares `[session.echo_commune]` but **no** `commune_dir` still",
    "echoes — the echo needs no directory. A missing `[session.echo_commune]` role is",
    "a **loud once-skip**, not a retried fault.",
)

sub(OLD_HEAD, NEW_HEAD, "protocol-section")

# ---------------------------------------------------------- invariant 3 head
sub(
    L(
        "**3. Ingest deletes the drop.** On its next pulse tick spt-core reads the",
        "drop, routes it into the durable context tiers, and **deletes the file** —",
    ),
    L(
        "**3. Ingest deletes the agent's drop.** On its next pulse tick spt-core reads",
        "the agent's drop, routes it into the durable context tiers, and **deletes the",
        "file** —",
    ),
    "invariant-3",
)

# --------------------------------------------------------------- stdout section
sub(
    L(
        "The summarizer's **stdout is the brief** — the cheap-model synthesis of the",
        "session, as plain text. spt-core does not require a structured format at write",
        "time; it stamps a provenance header (`Source: echo-commune`) and writes the",
        "result as the commune drop file.",
        "",
        "On the later ingest tick that body is parsed with the **two-slice envelope**",
        "grammar, the same one a hand-written commune uses:",
    ),
    L(
        "The summarizer's **stdout is the brief** — the cheap-model synthesis of the",
        "session, as plain text. spt-core does not require a structured format; it",
        "stamps a provenance header (`Source: echo-commune`) and routes the result",
        "into the durable tiers.",
        "",
        "That body is parsed, at that moment, with the **two-slice envelope** grammar,",
        "the same one a hand-written commune uses:",
    ),
    "stdout-section",
)

sub(
    L(
        "Every write is precedence-guarded — a stale snapshot arriving inside another",
        "writer's protection window is suppressed (but still consumed and deleted). The",
    ),
    L(
        "Every write is precedence-guarded — a stale snapshot arriving inside another",
        "writer's protection window is suppressed (but still consumed). An endpoint with",
        "**no project anchor** (a psyche host, the engine room) has no project tier to",
        "fill, so a `<project-context>` slice in an echo brief is discarded with a loud",
        "`ECHO_PROJECT_UNROUTABLE` line rather than held as a file; the `COMMUNE` I/O",
        "event still carries the brief verbatim, so the funnel remains the record. The",
    ),
    "precedence-para",
)

# ------------------------------------------------------------------ in one line
sub(
    L(
        "Declare `[session.echo_commune]` with a command that **self-sources its",
        "transcript** (found via a `direction = \"read\"` locator key) and **prints the",
        "brief to stdout**; declare an **absolute** `commune_dir`; let spt-core do the",
        "spawn, the file-drop, the ingest, and the delete. That is the whole contract.",
    ),
    L(
        "Declare `[session.echo_commune]` with a command that **self-sources its",
        "transcript** (found via a `direction = \"read\"` locator key) and **prints the",
        "brief to stdout**; let spt-core do the spawn and route the brief into the",
        "durable tiers. That is the whole contract. `commune_dir` is a separate",
        "declaration for your **agent's own** communes — declare it absolute — and the",
        "echo neither needs it nor writes to it.",
    ),
    "in-one-line",
)

with io.open(P, "w", encoding="utf-8", newline="") as f:
    f.write(src)

print("echo-commune.md rewritten")
