--- title: "The writing standard, and what checks it" type: convention tags: [signpost, docs, convention] proposed_by: tok_bc09b340db294c709763789154d8113d model: kmem-docs-publisher extracted_at: "2026-10-08T22:29:42.733Z" --- # The writing standard, and what checks it **Written 2026-08-14.** [`../design/DESIGN.md`](../design/DESIGN.md) and [`../design/copy.md`](../design/copy.md) are the voice: precise, confident, unembellished; errors state cause and then fix; concede limits plainly; sentence case; no exclamation marks. Those rules are right, and they are the design source of truth for interface copy. This document is the part they do not have. It exists because four things a writing standard needs are absent from those rules: the scope, the named standard, the register of accepted exceptions, and the honest state of the tooling. --- ## 1. The scope is wider than the design system's, and the reason is retrieval rather than taste `design/copy.md` governs **interface copy**: buttons, error messages, empty states. It says nothing about the text this product is actually judged on, and that text is now the majority of what kmem emits: | Surface | Size, measured 2026-08-14 | Who reads it | | --- | --- | --- | | `docs/` | 1,388,505 bytes over 21 files (`wc -c docs/*.md`) | A person resuming the project, and an agent every session | | MCP tool definitions | 1,458 tokens (`node apps/worker/scripts/tool-footprint.mjs --check`) | Every model that connects, on every turn | | The Quick Connect briefing | `/s/{token}/AGENTS.md` | Every model that is given a link | | Refusal strings | `message` and `fix` on every error, in three dialects | Both | | `../CLAUDE.md`, which imports `../AGENTS.md` | 341 and 48 lines on 2026-10-09, 21 of them projected by the product itself | Every coding agent | **The argument is measurable, not aesthetic.** BM25 rewards a term that means one thing; a bi-encoder rewards a phrase used consistently. This repository's documentation *is* its eval corpus, so a vocabulary that drifts is a retrieval defect that shows up in `precision@1`, and one that tightens is a retrieval improvement. That is a stronger reason to hold a standard than readability, and it is the one that applies to text a model reads. Drift was real. On 2026-08-14 one object had three names: "session link" in 97 files, "Quick Connect link" in 22 and "share link" in 12. The owner settled the names on 2026-09-16. Use these words, and only these, in text a person or a model reads: | Thing | The word to use | Do not write | | --- | --- | --- | | What a person creates in Connect, one per agent | **connection** | session link, share link, token grant | | The `sp_` secret of a connection | **token** | connection token, session token, link | | The door that serves a briefing at a URL | **Quick Connect** | connection document, connection docs | | The URL of that door, which holds the token | **Quick Connect URL** | connection URL, connection link, share link | | The Markdown served at that URL | **Quick Connect document** | connection document | | The environment variables that hold a token and an instance URL | **`KMEM_TOKEN`**, **`KMEM_URL`** | `SIGNPOST_TOKEN`, `SIGNPOST_URL`. The stdio bridge still reads them after the `KMEM_` names, so configurations people already have keep working | "Session link" stays the **internal** name: the `SessionLink` type, the `session_tokens` table, the `session-link-expired` error code and comments. None of those reaches a person as a word. `apps/web/test/screens/connect-terms.test.ts` refuses the old names in every value of `en.json`, in every string of `apps/web/src` and `apps/worker/src`, in the stdio bridge and in the generated `docs/API.md`. It excuses two code lines, and it names each one and its reason. ## 2. There is a named standard, and it is settleable **ASD-STE100 Simplified Technical English.** Naming a standard is what turns "write clearly" from a preference into something two people can settle an argument with. Four of its rules matter most here and none of them can be checked by a tool: 1. **One word, one meaning, across the whole corpus.** The table above is what violating it looks like. 2. **A noun cluster of three words or fewer.** "Quick Connect briefing cache invalidation" is five. 3. **One instruction per sentence.** 4. **A warning starts with the command.** "Do not run `pnpm test:remote`" before the reason, not after it. The design system's "errors state cause and fix" is the same rule for errors; this is it for warnings. Two rules of the design system that ASD-STE100 does not have, and which stay: sentence case, and no emoji anywhere. **Two departures from the standard, each with its reason**, because a standard adopted without stated departures is a standard somebody quietly abandons: - **Sentence length is capped at 25 words, not 20.** ASD-STE100 rule 6.1 caps an instruction at 20 and rule 6.2 caps a descriptive sentence at 25. Most prose here is descriptive. Read a 21-word instruction as a real failure even so. - **Nominalisations that are technical names stay.** `provenance`, `attribution`, `revocation`, `resolution`, `inheritance`, `verification`, `documentation`, `evaluation`, `concentration` — each is the name of a thing in this product, not a weak noun hiding a verb. - **"rather" is not a weasel word here.** It is used in the comparative — "X rather than Y" — which names a rejected alternative, and naming the rejected alternative is what every ADR in [`ADR/`](./ADR) exists to do. ## 3. The findings that stay, and why each one stays **This is the part worth copying, and it is the part the design system has no equivalent of.** A style rule with no register of accepted exceptions produces one of two failures: somebody "fixes" a deliberate choice, or somebody stops reading the findings. So the register carries a reason per row, and the next reader argues with the reason rather than re-litigating the finding. | Finding | Where | Why it stays | | --- | --- | --- | | Sentences over 25 words | Throughout `docs/` | Every one that remains carries a measurement or a caveat that a split would separate from its claim. A number without the condition it was measured under is worse than a long sentence | | Passive "is redacted", "are unlimited", "is restricted" | `SECURITY.md`, `storage/audit.ts` | Predicate adjectives, not the passive voice. "The value is redacted" states a property, the same way "the note is short" does | | Passive "was written" | `MCP.md`, `API.md` | Quoted product strings. `remember` answers "Nothing was written." Changing the document would make it disagree with the code | | Noun clusters of four | `API.md` | Generated from Zod `.describe()` text. The fix belongs in the schema, not the document — `API.md` is compared byte for byte against its generator | | "Quick Connect" in title case | Everywhere | A proper noun: the name of one of the two doors. The design system's sentence-case rule is about UI labels, not names | | "session link" in code and comments | `apps/`, `packages/` | The internal name, as section 1 says. A person never reads it, and renaming the type and the table would be a migration for no reader | | "connection document" in older public docs | `docs/` other than `API.md` and the user guide | Historical prose and ADR titles still use it. Current product copy says Quick Connect. `API.md` is clean: its words come from the Zod `.describe()` text, which the scan holds | **Add a row before you "fix" a finding.** A new finding is a real one until it is in this table with a reason. ## 4. What no tool can check, and what one could **Nothing lints prose in this repository today.** `pnpm lint` is Biome; it reads TypeScript, JSON and formatting, and it has never looked at a sentence. That is the honest state and it is why this document is a standard plus a register rather than a command. `wsc-lint` from `wsc.theserverless.dev`, run against a committed `.wscrc.json`, is the right shape: a config in the repository, with a reason recorded for every default it changes. **It is not adopted here yet, for two reasons that are conditions rather than objections:** 1. **Its licence has not been checked** against this repository's rule — Apache-2.0 project, no GPL, AGPL or LGPL dependency (`pnpm licence-check`). 2. **It is invoked through an unpinned `npx`**, so the version that runs in CI is whatever was published that morning. A check that can change its own answer without a diff is not a check. Both are cheap to close. Until they are, the register above is maintained by reading, and the four rules in section 2 are the ones to read for — they are the ones a linter could not check anyway. --- ## What this does not change `design/DESIGN.md` and `design/copy.md` stay the authority for interface copy and this document does not restate them. Where the two could be read as disagreeing, the design source of truth wins for anything a person sees in the product, and this document governs the text a model reads. They have not disagreed yet. **Read next** - [`../design/copy.md`](../design/copy.md) — the canonical strings, and the voice rules above them - [`../design/DESIGN.md`](../design/DESIGN.md) — the rest of the design source of truth - [`CONVENTIONS.md`](./CONVENTIONS.md) — how the documents relate, which is the structural half of the same problem