diff --git a/Cargo.lock b/Cargo.lock
index 05cb3b7..716669a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3143,7 +3143,7 @@ dependencies = [
[[package]]
name = "tw-api"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"serde",
"serde_json",
@@ -3154,7 +3154,7 @@ dependencies = [
[[package]]
name = "tw-bedrock"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -3175,7 +3175,7 @@ dependencies = [
[[package]]
name = "tw-breaker"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"serde",
"serde_json",
@@ -3183,7 +3183,7 @@ dependencies = [
[[package]]
name = "tw-config"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"blake3",
"libc",
@@ -3208,7 +3208,7 @@ dependencies = [
[[package]]
name = "tw-control"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"axum",
"base64",
@@ -3251,7 +3251,7 @@ dependencies = [
[[package]]
name = "tw-dialect"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"memchr",
"serde",
@@ -3260,7 +3260,7 @@ dependencies = [
[[package]]
name = "tw-engine"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"chrono",
"serde",
@@ -3274,7 +3274,7 @@ dependencies = [
[[package]]
name = "tw-gateway"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"arc-swap",
"async-stream",
@@ -3325,7 +3325,7 @@ dependencies = [
[[package]]
name = "tw-guard"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"aho-corasick",
"base64",
@@ -3343,7 +3343,7 @@ dependencies = [
[[package]]
name = "tw-link"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"serde",
"serde_json",
@@ -3357,7 +3357,7 @@ dependencies = [
[[package]]
name = "tw-observe"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"tokio",
"tracing",
@@ -3366,7 +3366,7 @@ dependencies = [
[[package]]
name = "tw-plugin"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"libc",
"rand 0.10.2",
@@ -3381,7 +3381,7 @@ dependencies = [
[[package]]
name = "tw-pricing"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"arc-swap",
"flate2",
@@ -3394,14 +3394,14 @@ dependencies = [
[[package]]
name = "tw-secret"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"thiserror",
]
[[package]]
name = "tw-store"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"blake3",
"bytes",
@@ -3421,7 +3421,7 @@ dependencies = [
[[package]]
name = "tw-types"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"serde",
"serde_json",
@@ -3430,7 +3430,7 @@ dependencies = [
[[package]]
name = "tw-watch"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"libc",
"notify",
@@ -3441,7 +3441,7 @@ dependencies = [
[[package]]
name = "tw-yaml"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"saphyr-parser",
"serde_yaml_ng",
@@ -3451,7 +3451,7 @@ dependencies = [
[[package]]
name = "twcore"
-version = "0.73.0"
+version = "0.74.0"
dependencies = [
"anyhow",
"axum",
diff --git a/Cargo.toml b/Cargo.toml
index 6a343e3..b48580f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,7 +54,7 @@ categories = ["network-programming", "web-programming::http-server"]
# 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。
# 这里是 crate 的版本。
-version = "0.73.0"
+version = "0.74.0"
[workspace.dependencies]
# ── 内部 crate ───────────────────────────────────────────
diff --git a/docs/config.md b/docs/config.md
index df23ec8..7ef1c66 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -756,14 +756,16 @@ refuses, deletes, replaces or only records.
#### `security.redact`
-Before a request leaves, the whole request (system prompt, earlier answers
-and tool calls included) is searched for credentials and personal
-information. Under `enforce` what a rule finds is replaced with placeholders,
-and put back where the answer repeats them; what a rule with `action: record`
-finds is sent as it is and only recorded. Images, files and other base64
-payloads are not searched, nor are field names; custom rules also leave alone
-the identifiers a protocol needs as they are, such as item and call ids,
-types, roles, tool names and model names.
+Before a request leaves, the text written in it is searched for credentials
+and personal information: system prompts, the messages of every role
+(earlier answers included), reasoning, tool-call arguments, tool results and
+tool descriptions. Under `enforce` what a rule finds is replaced with
+placeholders, and put back where the answer repeats them; what a rule with
+`action: record` finds is sent as it is and only recorded. Values the
+protocol needs as they are, such as ids, types, roles, tool names, model
+names and signatures, are never searched, nor are field names, images,
+files, settings, or parts of a request the gateway does not recognize as
+written text.
diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md
index 89df577..1940b24 100644
--- a/docs/config.zh-CN.md
+++ b/docs/config.zh-CN.md
@@ -561,7 +561,7 @@ pricing:
#### `security.redact`
-请求发出前,在整个请求中(包括系统提示、之前的回答和工具调用)查找凭据和个人信息。`enforce` 下将规则找到的内容替换为占位符,回答中重复出现时再换回原值;`action: record` 的规则找到的内容原样发送,只作记录。图片、文件等 base64 内容不查,字段名也不查;自定义规则还不查协议要求原样保留的标识,如条目和调用的 id、类型、角色、工具名、模型名。
+请求发出前,在请求中写出的正文里查找凭据和个人信息:系统提示、各角色的消息(包括之前的回答)、推理、工具调用的参数、工具结果和工具说明。`enforce` 下将规则找到的内容替换为占位符,回答中重复出现时再换回原值;`action: record` 的规则找到的内容原样发送,只作记录。协议要求原样保留的值(如 id、类型、角色、工具名、模型名、签名)一律不查,字段名、图片、文件、配置项以及网关不认作正文的部分也不查。
diff --git a/release-notes/0.74.0.md b/release-notes/0.74.0.md
new file mode 100644
index 0000000..3a44079
--- /dev/null
+++ b/release-notes/0.74.0.md
@@ -0,0 +1,18 @@
+This release makes outbound redaction look only at the text written in a request, located by the request's format, so it can no longer change an id or another value the protocol needs and get the request refused.
+
+**Upgrade notes**
+
+- The control-plane protocol version stays at 48; ThinkWatch Lite releases that include 0.73.0 connect to 0.74.0 and the other way round.
+- The request store's schema and the configuration are unchanged; nothing is cleared.
+- Requests to paths the gateway does not recognize, and WebSocket connections other than Responses and Realtime, are no longer searched by redaction; they were searched as whole text before.
+- Request fields outside the written text, such as `metadata`, are no longer searched.
+- For crates that embed the gateway: `tw_guard::redact::flow::find`, `look`, `look_from`, `look_hits` and `replace` take the request's `Shape`, and `reported` takes a `Scope`. `hits` still searches the whole text; `hits_in` searches a `Scope`. New module `tw_dialect::written` with `Shape`, `spots` and `ranges`.
+
+**Redaction searches only written text.** Rules used to run over the whole JSON body, with field names and a fixed list of identifier fields left out for custom rules only. A built-in rule could still match inside a protocol value: with the mobile-number rule on, eleven digits inside a Codex reasoning item id (`rs_` followed by hexadecimal) were replaced with a placeholder, the upstream refused the id, and since the history is sent again on every turn, every later turn of that conversation was refused too.
+
+Redaction now searches only the text people, models and tools wrote:
+- Anthropic, Chat Completions, Responses, Gemini and Converse requests: system prompts and instructions, the text of every message whatever its role, reasoning text and summaries, tool-call arguments and inputs, tool results, and tool descriptions.
+- Embeddings (`input`), legacy completions (`prompt` and `suffix`), Gemini embeddings, and the client events of a Realtime connection.
+- Ids, types, roles, names, model names, signatures, encrypted content, images and files, and settings are never searched, and neither are blocks or fields not on this list.
+
+Reasoning text stays on the list: placeholders in an answer are restored before the client sees it, and when the client sends the history back, the values in it are replaced again. A request converted to another format is searched as the format it is sent in. Stored bodies and security-log excerpts are still masked as whole text.